@mseep/clawdcursor 1.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2264 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/SECURITY.md +44 -0
- package/SKILL.md +503 -0
- package/dist/core/agent-loop/agent.d.ts +42 -0
- package/dist/core/agent-loop/agent.js +1023 -0
- package/dist/core/agent-loop/agent.js.map +1 -0
- package/dist/core/agent-loop/batch-tool.d.ts +25 -0
- package/dist/core/agent-loop/batch-tool.js +218 -0
- package/dist/core/agent-loop/batch-tool.js.map +1 -0
- package/dist/core/agent-loop/coord-scale.d.ts +72 -0
- package/dist/core/agent-loop/coord-scale.js +89 -0
- package/dist/core/agent-loop/coord-scale.js.map +1 -0
- package/dist/core/agent-loop/focus-guard.d.ts +24 -0
- package/dist/core/agent-loop/focus-guard.js +29 -0
- package/dist/core/agent-loop/focus-guard.js.map +1 -0
- package/dist/core/agent-loop/project-mcp.d.ts +97 -0
- package/dist/core/agent-loop/project-mcp.js +253 -0
- package/dist/core/agent-loop/project-mcp.js.map +1 -0
- package/dist/core/agent-loop/prompt.d.ts +45 -0
- package/dist/core/agent-loop/prompt.js +426 -0
- package/dist/core/agent-loop/prompt.js.map +1 -0
- package/dist/core/agent-loop/tool-meta.d.ts +93 -0
- package/dist/core/agent-loop/tool-meta.js +651 -0
- package/dist/core/agent-loop/tool-meta.js.map +1 -0
- package/dist/core/agent-loop/tools.d.ts +38 -0
- package/dist/core/agent-loop/tools.js +2134 -0
- package/dist/core/agent-loop/tools.js.map +1 -0
- package/dist/core/agent-loop/types.d.ts +170 -0
- package/dist/core/agent-loop/types.js +12 -0
- package/dist/core/agent-loop/types.js.map +1 -0
- package/dist/core/agent.d.ts +51 -0
- package/dist/core/agent.js +245 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/app-categories.d.ts +67 -0
- package/dist/core/app-categories.js +108 -0
- package/dist/core/app-categories.js.map +1 -0
- package/dist/core/banner.d.ts +70 -0
- package/dist/core/banner.js +245 -0
- package/dist/core/banner.js.map +1 -0
- package/dist/core/classify/capability.d.ts +45 -0
- package/dist/core/classify/capability.js +78 -0
- package/dist/core/classify/capability.js.map +1 -0
- package/dist/core/decompose/llm-decomposer.d.ts +35 -0
- package/dist/core/decompose/llm-decomposer.js +156 -0
- package/dist/core/decompose/llm-decomposer.js.map +1 -0
- package/dist/core/decompose/parser.d.ts +27 -0
- package/dist/core/decompose/parser.js +101 -0
- package/dist/core/decompose/parser.js.map +1 -0
- package/dist/core/observability/correlation.d.ts +19 -0
- package/dist/core/observability/correlation.js +36 -0
- package/dist/core/observability/correlation.js.map +1 -0
- package/dist/core/observability/cost-meter.d.ts +51 -0
- package/dist/core/observability/cost-meter.js +134 -0
- package/dist/core/observability/cost-meter.js.map +1 -0
- package/dist/core/observability/logger.d.ts +61 -0
- package/dist/core/observability/logger.js +550 -0
- package/dist/core/observability/logger.js.map +1 -0
- package/dist/core/router/aliases.d.ts +50 -0
- package/dist/core/router/aliases.js +104 -0
- package/dist/core/router/aliases.js.map +1 -0
- package/dist/core/router/normalize.d.ts +41 -0
- package/dist/core/router/normalize.js +80 -0
- package/dist/core/router/normalize.js.map +1 -0
- package/dist/core/safety.d.ts +126 -0
- package/dist/core/safety.js +568 -0
- package/dist/core/safety.js.map +1 -0
- package/dist/core/sense/a11y-resolver.d.ts +73 -0
- package/dist/core/sense/a11y-resolver.js +76 -0
- package/dist/core/sense/a11y-resolver.js.map +1 -0
- package/dist/core/sense/fingerprint.d.ts +41 -0
- package/dist/core/sense/fingerprint.js +123 -0
- package/dist/core/sense/fingerprint.js.map +1 -0
- package/dist/core/sense/rank.d.ts +70 -0
- package/dist/core/sense/rank.js +192 -0
- package/dist/core/sense/rank.js.map +1 -0
- package/dist/core/sense/reactive-check.d.ts +40 -0
- package/dist/core/sense/reactive-check.js +48 -0
- package/dist/core/sense/reactive-check.js.map +1 -0
- package/dist/core/sense/snapshot.d.ts +19 -0
- package/dist/core/sense/snapshot.js +100 -0
- package/dist/core/sense/snapshot.js.map +1 -0
- package/dist/core/sense/types.d.ts +66 -0
- package/dist/core/sense/types.js +9 -0
- package/dist/core/sense/types.js.map +1 -0
- package/dist/core/sense/ui-map-anchors.d.ts +7 -0
- package/dist/core/sense/ui-map-anchors.js +24 -0
- package/dist/core/sense/ui-map-anchors.js.map +1 -0
- package/dist/core/sense/ui-map-elements.d.ts +5 -0
- package/dist/core/sense/ui-map-elements.js +33 -0
- package/dist/core/sense/ui-map-elements.js.map +1 -0
- package/dist/core/sense/ui-map-find.d.ts +56 -0
- package/dist/core/sense/ui-map-find.js +153 -0
- package/dist/core/sense/ui-map-find.js.map +1 -0
- package/dist/core/sense/ui-map-fuse.d.ts +4 -0
- package/dist/core/sense/ui-map-fuse.js +44 -0
- package/dist/core/sense/ui-map-fuse.js.map +1 -0
- package/dist/core/sense/ui-map-geom.d.ts +3 -0
- package/dist/core/sense/ui-map-geom.js +16 -0
- package/dist/core/sense/ui-map-geom.js.map +1 -0
- package/dist/core/sense/ui-map-holder.d.ts +58 -0
- package/dist/core/sense/ui-map-holder.js +87 -0
- package/dist/core/sense/ui-map-holder.js.map +1 -0
- package/dist/core/sense/ui-map-normalize.d.ts +19 -0
- package/dist/core/sense/ui-map-normalize.js +65 -0
- package/dist/core/sense/ui-map-normalize.js.map +1 -0
- package/dist/core/sense/ui-map-render.d.ts +4 -0
- package/dist/core/sense/ui-map-render.js +34 -0
- package/dist/core/sense/ui-map-render.js.map +1 -0
- package/dist/core/sense/ui-map-resolve.d.ts +41 -0
- package/dist/core/sense/ui-map-resolve.js +59 -0
- package/dist/core/sense/ui-map-resolve.js.map +1 -0
- package/dist/core/sense/ui-map-types.d.ts +66 -0
- package/dist/core/sense/ui-map-types.js +11 -0
- package/dist/core/sense/ui-map-types.js.map +1 -0
- package/dist/core/sense/ui-map.d.ts +29 -0
- package/dist/core/sense/ui-map.js +113 -0
- package/dist/core/sense/ui-map.js.map +1 -0
- package/dist/core/verify/assertions.d.ts +132 -0
- package/dist/core/verify/assertions.js +284 -0
- package/dist/core/verify/assertions.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/browser-config.d.ts +36 -0
- package/dist/llm/browser-config.js +83 -0
- package/dist/llm/browser-config.js.map +1 -0
- package/dist/llm/client.d.ts +268 -0
- package/dist/llm/client.js +1094 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/config.d.ts +79 -0
- package/dist/llm/config.js +375 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/credentials.d.ts +35 -0
- package/dist/llm/credentials.js +491 -0
- package/dist/llm/credentials.js.map +1 -0
- package/dist/llm/external-creds.d.ts +42 -0
- package/dist/llm/external-creds.js +169 -0
- package/dist/llm/external-creds.js.map +1 -0
- package/dist/llm/providers.d.ts +123 -0
- package/dist/llm/providers.js +717 -0
- package/dist/llm/providers.js.map +1 -0
- package/dist/paths.d.ts +31 -0
- package/dist/paths.js +147 -0
- package/dist/paths.js.map +1 -0
- package/dist/platform/accessibility.d.ts +139 -0
- package/dist/platform/accessibility.js +670 -0
- package/dist/platform/accessibility.js.map +1 -0
- package/dist/platform/cdp-driver.d.ts +318 -0
- package/dist/platform/cdp-driver.js +1179 -0
- package/dist/platform/cdp-driver.js.map +1 -0
- package/dist/platform/index.d.ts +11 -0
- package/dist/platform/index.js +69 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/keys.d.ts +17 -0
- package/dist/platform/keys.js +129 -0
- package/dist/platform/keys.js.map +1 -0
- package/dist/platform/launch-poll.d.ts +101 -0
- package/dist/platform/launch-poll.js +177 -0
- package/dist/platform/launch-poll.js.map +1 -0
- package/dist/platform/linux.d.ts +173 -0
- package/dist/platform/linux.js +1253 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/macos.d.ts +136 -0
- package/dist/platform/macos.js +976 -0
- package/dist/platform/macos.js.map +1 -0
- package/dist/platform/native-desktop.d.ts +145 -0
- package/dist/platform/native-desktop.js +936 -0
- package/dist/platform/native-desktop.js.map +1 -0
- package/dist/platform/native-helper.d.ts +130 -0
- package/dist/platform/native-helper.js +592 -0
- package/dist/platform/native-helper.js.map +1 -0
- package/dist/platform/ocr-engine.d.ts +78 -0
- package/dist/platform/ocr-engine.js +363 -0
- package/dist/platform/ocr-engine.js.map +1 -0
- package/dist/platform/ps-runner.d.ts +28 -0
- package/dist/platform/ps-runner.js +228 -0
- package/dist/platform/ps-runner.js.map +1 -0
- package/dist/platform/types.d.ts +397 -0
- package/dist/platform/types.js +15 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/platform/uri-handler.d.ts +75 -0
- package/dist/platform/uri-handler.js +273 -0
- package/dist/platform/uri-handler.js.map +1 -0
- package/dist/platform/wayland-backend.d.ts +53 -0
- package/dist/platform/wayland-backend.js +348 -0
- package/dist/platform/wayland-backend.js.map +1 -0
- package/dist/platform/windows.d.ts +232 -0
- package/dist/platform/windows.js +1210 -0
- package/dist/platform/windows.js.map +1 -0
- package/dist/postbuild.d.ts +10 -0
- package/dist/postbuild.js +98 -0
- package/dist/postbuild.js.map +1 -0
- package/dist/schema/snapshot.d.ts +33 -0
- package/dist/schema/snapshot.js +90 -0
- package/dist/schema/snapshot.js.map +1 -0
- package/dist/shortcuts.d.ts +30 -0
- package/dist/shortcuts.js +261 -0
- package/dist/shortcuts.js.map +1 -0
- package/dist/surface/cli.d.ts +7 -0
- package/dist/surface/cli.js +1556 -0
- package/dist/surface/cli.js.map +1 -0
- package/dist/surface/dashboard.d.ts +8 -0
- package/dist/surface/dashboard.js +1193 -0
- package/dist/surface/dashboard.js.map +1 -0
- package/dist/surface/doctor.d.ts +29 -0
- package/dist/surface/doctor.js +1514 -0
- package/dist/surface/doctor.js.map +1 -0
- package/dist/surface/format.d.ts +10 -0
- package/dist/surface/format.js +37 -0
- package/dist/surface/format.js.map +1 -0
- package/dist/surface/http-utility.d.ts +65 -0
- package/dist/surface/http-utility.js +336 -0
- package/dist/surface/http-utility.js.map +1 -0
- package/dist/surface/mcp-server.d.ts +91 -0
- package/dist/surface/mcp-server.js +280 -0
- package/dist/surface/mcp-server.js.map +1 -0
- package/dist/surface/onboarding.d.ts +15 -0
- package/dist/surface/onboarding.js +184 -0
- package/dist/surface/onboarding.js.map +1 -0
- package/dist/surface/pidfile.d.ts +79 -0
- package/dist/surface/pidfile.js +263 -0
- package/dist/surface/pidfile.js.map +1 -0
- package/dist/surface/readiness.d.ts +45 -0
- package/dist/surface/readiness.js +230 -0
- package/dist/surface/readiness.js.map +1 -0
- package/dist/surface/report.d.ts +68 -0
- package/dist/surface/report.js +341 -0
- package/dist/surface/report.js.map +1 -0
- package/dist/surface/skill-register.d.ts +14 -0
- package/dist/surface/skill-register.js +150 -0
- package/dist/surface/skill-register.js.map +1 -0
- package/dist/surface/version.d.ts +6 -0
- package/dist/surface/version.js +27 -0
- package/dist/surface/version.js.map +1 -0
- package/dist/tools/a11y.d.ts +8 -0
- package/dist/tools/a11y.js +545 -0
- package/dist/tools/a11y.js.map +1 -0
- package/dist/tools/a11y_depth.d.ts +19 -0
- package/dist/tools/a11y_depth.js +455 -0
- package/dist/tools/a11y_depth.js.map +1 -0
- package/dist/tools/agent.d.ts +15 -0
- package/dist/tools/agent.js +248 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/batch.d.ts +46 -0
- package/dist/tools/batch.js +230 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/cdp.d.ts +8 -0
- package/dist/tools/cdp.js +233 -0
- package/dist/tools/cdp.js.map +1 -0
- package/dist/tools/compact.d.ts +63 -0
- package/dist/tools/compact.js +418 -0
- package/dist/tools/compact.js.map +1 -0
- package/dist/tools/cost-class.d.ts +38 -0
- package/dist/tools/cost-class.js +117 -0
- package/dist/tools/cost-class.js.map +1 -0
- package/dist/tools/desktop.d.ts +9 -0
- package/dist/tools/desktop.js +346 -0
- package/dist/tools/desktop.js.map +1 -0
- package/dist/tools/electron_bridge.d.ts +41 -0
- package/dist/tools/electron_bridge.js +261 -0
- package/dist/tools/electron_bridge.js.map +1 -0
- package/dist/tools/extras.d.ts +22 -0
- package/dist/tools/extras.js +942 -0
- package/dist/tools/extras.js.map +1 -0
- package/dist/tools/favorites.d.ts +13 -0
- package/dist/tools/favorites.js +137 -0
- package/dist/tools/favorites.js.map +1 -0
- package/dist/tools/introspection.d.ts +13 -0
- package/dist/tools/introspection.js +55 -0
- package/dist/tools/introspection.js.map +1 -0
- package/dist/tools/ocr.d.ts +8 -0
- package/dist/tools/ocr.js +66 -0
- package/dist/tools/ocr.js.map +1 -0
- package/dist/tools/orchestration.d.ts +7 -0
- package/dist/tools/orchestration.js +377 -0
- package/dist/tools/orchestration.js.map +1 -0
- package/dist/tools/playbooks/extract-compose.d.ts +22 -0
- package/dist/tools/playbooks/extract-compose.js +85 -0
- package/dist/tools/playbooks/extract-compose.js.map +1 -0
- package/dist/tools/playbooks/find-replace.d.ts +11 -0
- package/dist/tools/playbooks/find-replace.js +56 -0
- package/dist/tools/playbooks/find-replace.js.map +1 -0
- package/dist/tools/playbooks/index.d.ts +63 -0
- package/dist/tools/playbooks/index.js +70 -0
- package/dist/tools/playbooks/index.js.map +1 -0
- package/dist/tools/playbooks/keys-blocklist.d.ts +24 -0
- package/dist/tools/playbooks/keys-blocklist.js +89 -0
- package/dist/tools/playbooks/keys-blocklist.js.map +1 -0
- package/dist/tools/registry.d.ts +40 -0
- package/dist/tools/registry.js +560 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/safety-gate.d.ts +16 -0
- package/dist/tools/safety-gate.js +70 -0
- package/dist/tools/safety-gate.js.map +1 -0
- package/dist/tools/scheduler.d.ts +76 -0
- package/dist/tools/scheduler.js +413 -0
- package/dist/tools/scheduler.js.map +1 -0
- package/dist/tools/shortcuts.d.ts +13 -0
- package/dist/tools/shortcuts.js +205 -0
- package/dist/tools/shortcuts.js.map +1 -0
- package/dist/tools/smart.d.ts +15 -0
- package/dist/tools/smart.js +785 -0
- package/dist/tools/smart.js.map +1 -0
- package/dist/tools/types.d.ts +174 -0
- package/dist/tools/types.js +67 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/window-text.d.ts +15 -0
- package/dist/tools/window-text.js +39 -0
- package/dist/tools/window-text.js.map +1 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/native/Package.swift +38 -0
- package/native/README.md +113 -0
- package/native/Sources/ClawdCursorHelper/main.swift +602 -0
- package/native/Sources/ClawdCursorHost/main.swift +182 -0
- package/native/Sources/PermissionCheck/main.swift +53 -0
- package/native/Sources/ScreenshotHelper/main.swift +219 -0
- package/native/build.sh +139 -0
- package/native/entitlements.plist +12 -0
- package/package.json +115 -0
- package/scripts/banner.ps1 +112 -0
- package/scripts/coord-accuracy.ps1 +140 -0
- package/scripts/coord-uwp.ps1 +80 -0
- package/scripts/edge-glow.ps1 +180 -0
- package/scripts/find-element.ps1 +198 -0
- package/scripts/get-foreground-window.ps1 +71 -0
- package/scripts/get-screen-context.ps1 +183 -0
- package/scripts/get-windows.ps1 +66 -0
- package/scripts/install-panic-hotkey.ps1 +46 -0
- package/scripts/interact-element.ps1 +431 -0
- package/scripts/invoke-element.ps1 +314 -0
- package/scripts/linux/atspi-bridge.py +356 -0
- package/scripts/linux/ocr-recognize.py +154 -0
- package/scripts/mac/_window-picker.jxa +163 -0
- package/scripts/mac/find-element.jxa +0 -0
- package/scripts/mac/find-element.sh +161 -0
- package/scripts/mac/focus-window.jxa +284 -0
- package/scripts/mac/get-focused-element.jxa +102 -0
- package/scripts/mac/get-foreground-window.jxa +173 -0
- package/scripts/mac/get-screen-context.jxa +197 -0
- package/scripts/mac/get-ui-tree.sh +141 -0
- package/scripts/mac/get-windows.jxa +117 -0
- package/scripts/mac/interact-element.sh +235 -0
- package/scripts/mac/invoke-element.jxa +408 -0
- package/scripts/mac/ocr-recognize.swift +124 -0
- package/scripts/ocr-recognize.ps1 +102 -0
- package/scripts/postinstall-native.js +48 -0
- package/scripts/ps-bridge.ps1 +830 -0
- package/scripts/smoke-mcp.ps1 +119 -0
- package/scripts/sync-version.ts +178 -0
- package/scripts/verify-install.js +81 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
#!/usr/bin/env osascript -l JavaScript
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* invoke-element.jxa
|
|
5
|
+
*
|
|
6
|
+
* Finds a UI element and invokes an action on it.
|
|
7
|
+
*
|
|
8
|
+
* Parameters:
|
|
9
|
+
* -action <string> - Required. One of: click | focus | set-value | get-value |
|
|
10
|
+
* expand | collapse | toggle | select
|
|
11
|
+
* -name <string> - Match elements by name (contains match, case-insensitive)
|
|
12
|
+
* -role <string> - Match elements by role (e.g., "button", "text field")
|
|
13
|
+
* -processId <number> - Required. The process ID of the target application
|
|
14
|
+
* -value <string> - Value to set (only used with set-value action)
|
|
15
|
+
*
|
|
16
|
+
* Returns JSON result with success status and action-specific data.
|
|
17
|
+
*
|
|
18
|
+
* Usage: osascript -l JavaScript invoke-element.jxa -action click -name "OK" -processId 1234
|
|
19
|
+
* osascript -l JavaScript invoke-element.jxa -action set-value -name "Username" -value "john" -processId 1234
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Utility function to safely get property values
|
|
23
|
+
function safeGet(obj, property, defaultValue) {
|
|
24
|
+
try {
|
|
25
|
+
var val = obj[property]();
|
|
26
|
+
return val !== undefined && val !== null ? val : defaultValue;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return defaultValue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Find element matching criteria
|
|
33
|
+
function findElement(root, targetName, targetRole, maxDepth, currentDepth) {
|
|
34
|
+
if (currentDepth > maxDepth) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
var elName = '';
|
|
40
|
+
var elRole = '';
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
elName = root.name() || '';
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
elRole = root.role() || '';
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
|
|
50
|
+
// Check if this element matches
|
|
51
|
+
var nameMatch = !targetName ||
|
|
52
|
+
(elName && elName.toLowerCase().indexOf(targetName.toLowerCase()) !== -1);
|
|
53
|
+
var roleMatch = !targetRole ||
|
|
54
|
+
(elRole && elRole.toLowerCase().indexOf(targetRole.toLowerCase()) !== -1);
|
|
55
|
+
|
|
56
|
+
if (nameMatch && roleMatch && (elName || elRole)) {
|
|
57
|
+
return root;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Search children
|
|
61
|
+
try {
|
|
62
|
+
var children = root.uiElements();
|
|
63
|
+
for (var i = 0; i < children.length; i++) {
|
|
64
|
+
var found = findElement(children[i], targetName, targetRole, maxDepth, currentDepth + 1);
|
|
65
|
+
if (found) return found;
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {}
|
|
68
|
+
|
|
69
|
+
// Check specific element types
|
|
70
|
+
var elementTypes = ['buttons', 'textFields', 'popUpButtons', 'checkboxes', 'radioButtons', 'menus'];
|
|
71
|
+
for (var j = 0; j < elementTypes.length; j++) {
|
|
72
|
+
try {
|
|
73
|
+
var typedElements = root[elementTypes[j]]();
|
|
74
|
+
for (var k = 0; k < typedElements.length; k++) {
|
|
75
|
+
var found = findElement(typedElements[k], targetName, targetRole, maxDepth, currentDepth + 1);
|
|
76
|
+
if (found) return found;
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get element bounds for coordinate fallback
|
|
86
|
+
function getElementBounds(element) {
|
|
87
|
+
try {
|
|
88
|
+
if (element.position && element.size) {
|
|
89
|
+
var pos = element.position();
|
|
90
|
+
var sz = element.size();
|
|
91
|
+
return {
|
|
92
|
+
x: pos[0] || 0,
|
|
93
|
+
y: pos[1] || 0,
|
|
94
|
+
width: sz[0] || 0,
|
|
95
|
+
height: sz[1] || 0
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// run(argv) is the JXA entry point — osascript calls it and its return value becomes stdout.
|
|
103
|
+
// argv receives command-line arguments passed after the script path.
|
|
104
|
+
function run(argv) {
|
|
105
|
+
try {
|
|
106
|
+
// Parse arguments from argv array
|
|
107
|
+
var params = {
|
|
108
|
+
action: null,
|
|
109
|
+
name: null,
|
|
110
|
+
role: null,
|
|
111
|
+
processId: null,
|
|
112
|
+
value: null
|
|
113
|
+
};
|
|
114
|
+
// Windows ControlType → macOS AX role mapping (mirrors find-element.jxa)
|
|
115
|
+
var CONTROL_TYPE_MAP = {
|
|
116
|
+
'Button': 'button', 'Edit': 'text field', 'Document': 'web area',
|
|
117
|
+
'ComboBox': 'pop up button', 'CheckBox': 'checkbox',
|
|
118
|
+
'RadioButton': 'radio button', 'Hyperlink': 'link',
|
|
119
|
+
'MenuItem': 'menu item', 'Menu': 'menu', 'Tab': 'tab group',
|
|
120
|
+
'TabItem': 'radio button', 'ListItem': 'row', 'TreeItem': 'row',
|
|
121
|
+
'Slider': 'slider', 'ScrollBar': 'scroll bar',
|
|
122
|
+
'ToolBar': 'toolbar', 'StaticText': 'static text', 'Window': 'window'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
for (var i = 0; i < argv.length; i++) {
|
|
126
|
+
var a = argv[i];
|
|
127
|
+
var hasNext = i + 1 < argv.length;
|
|
128
|
+
if ((a === '-action' || a === '-Action') && hasNext) {
|
|
129
|
+
params.action = argv[++i];
|
|
130
|
+
} else if ((a === '-name' || a === '-Name') && hasNext) {
|
|
131
|
+
params.name = argv[++i];
|
|
132
|
+
} else if ((a === '-role') && hasNext) {
|
|
133
|
+
params.role = argv[++i];
|
|
134
|
+
} else if (a === '-ControlType' && hasNext) {
|
|
135
|
+
var ct = argv[++i];
|
|
136
|
+
params.role = CONTROL_TYPE_MAP[ct] || ct.toLowerCase();
|
|
137
|
+
} else if ((a === '-processId' || a === '-ProcessId' || a === '-FocusedProcessId') && hasNext) {
|
|
138
|
+
// -FocusedProcessId accepted as an alias for callers that share
|
|
139
|
+
// arg-building with get-screen-context.jxa (defense-in-depth).
|
|
140
|
+
params.processId = parseInt(argv[++i], 10);
|
|
141
|
+
} else if ((a === '-value' || a === '-Value') && hasNext) {
|
|
142
|
+
params.value = argv[++i];
|
|
143
|
+
}
|
|
144
|
+
// -AutomationId and -Restore have no macOS equivalent; silently ignore
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Validate required parameters
|
|
148
|
+
if (!params.action) {
|
|
149
|
+
return JSON.stringify({
|
|
150
|
+
success: false,
|
|
151
|
+
error: 'Missing required parameter: -action'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!params.processId) {
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
success: false,
|
|
158
|
+
error: 'Missing required parameter: -processId'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
var SystemEvents = Application('System Events');
|
|
163
|
+
SystemEvents.includeStandardAdditions = true;
|
|
164
|
+
|
|
165
|
+
// Find the target process
|
|
166
|
+
var targetProcess = null;
|
|
167
|
+
var processes = SystemEvents.processes.where({ unixId: params.processId });
|
|
168
|
+
|
|
169
|
+
if (processes.length === 0) {
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'No process found with ID ' + params.processId
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
targetProcess = processes[0];
|
|
177
|
+
|
|
178
|
+
// Find target element
|
|
179
|
+
var targetElement = null;
|
|
180
|
+
var windows = targetProcess.windows;
|
|
181
|
+
|
|
182
|
+
for (var w = 0; w < windows.length && !targetElement; w++) {
|
|
183
|
+
targetElement = findElement(windows[w], params.name, params.role, 10, 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!targetElement) {
|
|
187
|
+
var searchDesc = '';
|
|
188
|
+
if (params.name) searchDesc += 'name="' + params.name + '" ';
|
|
189
|
+
if (params.role) searchDesc += 'role="' + params.role + '" ';
|
|
190
|
+
return JSON.stringify({
|
|
191
|
+
success: false,
|
|
192
|
+
error: 'Element not found: ' + searchDesc.trim()
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Execute the requested action
|
|
197
|
+
var result = { success: false };
|
|
198
|
+
|
|
199
|
+
switch (params.action) {
|
|
200
|
+
case 'click':
|
|
201
|
+
try {
|
|
202
|
+
targetElement.click();
|
|
203
|
+
result = {
|
|
204
|
+
success: true,
|
|
205
|
+
action: 'click',
|
|
206
|
+
method: 'click()'
|
|
207
|
+
};
|
|
208
|
+
} catch (e) {
|
|
209
|
+
// Fallback: return bounds for coordinate click
|
|
210
|
+
var bounds = getElementBounds(targetElement);
|
|
211
|
+
if (bounds) {
|
|
212
|
+
result = {
|
|
213
|
+
success: false,
|
|
214
|
+
action: 'click',
|
|
215
|
+
error: 'Element does not support click(). Use coordinate click.',
|
|
216
|
+
clickPoint: {
|
|
217
|
+
x: bounds.x + Math.floor(bounds.width / 2),
|
|
218
|
+
y: bounds.y + Math.floor(bounds.height / 2)
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
} else {
|
|
222
|
+
result = {
|
|
223
|
+
success: false,
|
|
224
|
+
action: 'click',
|
|
225
|
+
error: 'Click failed and no bounds available: ' + e.toString()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'set-value':
|
|
232
|
+
if (params.value === null) {
|
|
233
|
+
result = {
|
|
234
|
+
success: false,
|
|
235
|
+
error: 'Value parameter required for set-value action'
|
|
236
|
+
};
|
|
237
|
+
} else {
|
|
238
|
+
try {
|
|
239
|
+
targetElement.value = params.value;
|
|
240
|
+
result = {
|
|
241
|
+
success: true,
|
|
242
|
+
action: 'set-value',
|
|
243
|
+
value: params.value
|
|
244
|
+
};
|
|
245
|
+
} catch (e) {
|
|
246
|
+
// Try setting through focused property
|
|
247
|
+
try {
|
|
248
|
+
targetElement.focused = true;
|
|
249
|
+
// Use System Events to type the value
|
|
250
|
+
var app = Application.currentApplication();
|
|
251
|
+
app.includeStandardAdditions = true;
|
|
252
|
+
app.keystroke(params.value);
|
|
253
|
+
result = {
|
|
254
|
+
success: true,
|
|
255
|
+
action: 'set-value',
|
|
256
|
+
value: params.value,
|
|
257
|
+
method: 'keystroke'
|
|
258
|
+
};
|
|
259
|
+
} catch (ke) {
|
|
260
|
+
result = {
|
|
261
|
+
success: false,
|
|
262
|
+
error: 'Failed to set value: ' + e.toString()
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'get-value':
|
|
270
|
+
// Prefer AXValue, but fall back to the name property when AXValue
|
|
271
|
+
// is absent OR empty. Some controls expose an AXValue that reads
|
|
272
|
+
// "" while the real text is only in the title/name — returning the
|
|
273
|
+
// empty string there caused false "value did not change" reads
|
|
274
|
+
// (parity with the Windows ValuePattern→TextPattern fix).
|
|
275
|
+
var gotVal = null, gotMethod = null;
|
|
276
|
+
try {
|
|
277
|
+
var val = targetElement.value();
|
|
278
|
+
if (val !== null && val !== undefined && String(val).length > 0) {
|
|
279
|
+
gotVal = val; gotMethod = 'AXValue';
|
|
280
|
+
}
|
|
281
|
+
} catch (e) { /* no AXValue — try name */ }
|
|
282
|
+
if (gotVal === null) {
|
|
283
|
+
try {
|
|
284
|
+
var name = targetElement.name();
|
|
285
|
+
if (name !== null && name !== undefined && String(name).length > 0) {
|
|
286
|
+
gotVal = name; gotMethod = 'name';
|
|
287
|
+
}
|
|
288
|
+
} catch (ne) { /* no name either */ }
|
|
289
|
+
}
|
|
290
|
+
if (gotVal !== null) {
|
|
291
|
+
result = { success: true, action: 'get-value', value: gotVal, method: gotMethod };
|
|
292
|
+
} else {
|
|
293
|
+
// Genuinely empty (e.g. an empty field) — report "" success, not an error.
|
|
294
|
+
result = { success: true, action: 'get-value', value: '', method: 'empty' };
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case 'focus':
|
|
299
|
+
try {
|
|
300
|
+
targetElement.focused = true;
|
|
301
|
+
result = {
|
|
302
|
+
success: true,
|
|
303
|
+
action: 'focus'
|
|
304
|
+
};
|
|
305
|
+
} catch (e) {
|
|
306
|
+
result = {
|
|
307
|
+
success: false,
|
|
308
|
+
error: 'Failed to set focus: ' + e.toString()
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
|
|
313
|
+
// ── Tranche 1A: AX Expand/Collapse/Toggle/Select ────────────
|
|
314
|
+
// macOS AX exposes these through attributes (`AXExpanded`,
|
|
315
|
+
// `AXValue` for checkboxes) rather than actions. JXA lets us
|
|
316
|
+
// set them directly. Failing gracefully when an element
|
|
317
|
+
// doesn't support the attribute is important — UIA-style
|
|
318
|
+
// pattern-query equivalents don't exist on AX, so we just
|
|
319
|
+
// attempt the assignment and catch.
|
|
320
|
+
case 'expand':
|
|
321
|
+
try {
|
|
322
|
+
targetElement.attributes['AXExpanded'].value = true;
|
|
323
|
+
result = { success: true, action: 'expand' };
|
|
324
|
+
} catch (e) {
|
|
325
|
+
try {
|
|
326
|
+
// Menu-style elements respond to AXShowMenu action.
|
|
327
|
+
targetElement.actions['AXShowMenu'].perform();
|
|
328
|
+
result = { success: true, action: 'expand', method: 'AXShowMenu' };
|
|
329
|
+
} catch (e2) {
|
|
330
|
+
result = {
|
|
331
|
+
success: false,
|
|
332
|
+
error: 'Element does not support expand (no AXExpanded attribute or AXShowMenu action)'
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
case 'collapse':
|
|
339
|
+
try {
|
|
340
|
+
targetElement.attributes['AXExpanded'].value = false;
|
|
341
|
+
result = { success: true, action: 'collapse' };
|
|
342
|
+
} catch (e) {
|
|
343
|
+
result = {
|
|
344
|
+
success: false,
|
|
345
|
+
error: 'Element does not support collapse (no AXExpanded attribute)'
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case 'toggle':
|
|
351
|
+
// Checkboxes / radios expose AXValue as 0/1.
|
|
352
|
+
try {
|
|
353
|
+
var currentValue;
|
|
354
|
+
try { currentValue = targetElement.attributes['AXValue'].value; }
|
|
355
|
+
catch (vErr) { currentValue = 0; }
|
|
356
|
+
var newValue = currentValue ? 0 : 1;
|
|
357
|
+
targetElement.attributes['AXValue'].value = newValue;
|
|
358
|
+
result = {
|
|
359
|
+
success: true,
|
|
360
|
+
action: 'toggle',
|
|
361
|
+
data: { toggleState: newValue ? 'On' : 'Off' }
|
|
362
|
+
};
|
|
363
|
+
} catch (e) {
|
|
364
|
+
// Fallback: invoke a click (AXPress) — matches UIA Toggle semantics.
|
|
365
|
+
try {
|
|
366
|
+
targetElement.actions['AXPress'].perform();
|
|
367
|
+
result = { success: true, action: 'toggle', method: 'AXPress' };
|
|
368
|
+
} catch (e2) {
|
|
369
|
+
result = {
|
|
370
|
+
success: false,
|
|
371
|
+
error: 'Element does not support toggle (no AXValue or AXPress)'
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'select':
|
|
378
|
+
// AX uses AXSelected attribute for list items / tabs.
|
|
379
|
+
try {
|
|
380
|
+
targetElement.attributes['AXSelected'].value = true;
|
|
381
|
+
result = { success: true, action: 'select' };
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Some pickers use AXPick / AXMenuItemMark attributes — try AXPress as last resort.
|
|
384
|
+
try {
|
|
385
|
+
targetElement.actions['AXPress'].perform();
|
|
386
|
+
result = { success: true, action: 'select', method: 'AXPress' };
|
|
387
|
+
} catch (e2) {
|
|
388
|
+
result = {
|
|
389
|
+
success: false,
|
|
390
|
+
error: 'Element does not support select (no AXSelected attribute or AXPress action)'
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
|
|
396
|
+
default:
|
|
397
|
+
result = {
|
|
398
|
+
success: false,
|
|
399
|
+
error: 'Unknown action: ' + params.action
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return JSON.stringify(result);
|
|
404
|
+
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return JSON.stringify({ success: false, error: error.toString() });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
// macOS OCR via Vision framework (VNRecognizeTextRequest)
|
|
3
|
+
// Takes an image path, outputs JSON result to stdout.
|
|
4
|
+
// Matches the same JSON format as ocr-recognize.ps1 (Windows).
|
|
5
|
+
//
|
|
6
|
+
// Usage: swift ocr-recognize.swift /path/to/image.png
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import Vision
|
|
10
|
+
import AppKit
|
|
11
|
+
|
|
12
|
+
guard CommandLine.arguments.count > 1 else {
|
|
13
|
+
let err: [String: Any] = ["error": "Usage: ocr-recognize.swift <image-path>"]
|
|
14
|
+
if let data = try? JSONSerialization.data(withJSONObject: err),
|
|
15
|
+
let str = String(data: data, encoding: .utf8) {
|
|
16
|
+
print(str)
|
|
17
|
+
}
|
|
18
|
+
exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let imagePath = CommandLine.arguments[1]
|
|
22
|
+
let imageURL = URL(fileURLWithPath: imagePath)
|
|
23
|
+
|
|
24
|
+
guard let image = NSImage(contentsOf: imageURL),
|
|
25
|
+
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
|
26
|
+
let err: [String: Any] = ["error": "Failed to load image: \(imagePath)"]
|
|
27
|
+
if let data = try? JSONSerialization.data(withJSONObject: err),
|
|
28
|
+
let str = String(data: data, encoding: .utf8) {
|
|
29
|
+
print(str)
|
|
30
|
+
}
|
|
31
|
+
exit(0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let imageWidth = CGFloat(cgImage.width)
|
|
35
|
+
let imageHeight = CGFloat(cgImage.height)
|
|
36
|
+
|
|
37
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
38
|
+
var elements: [[String: Any]] = []
|
|
39
|
+
var fullText = ""
|
|
40
|
+
|
|
41
|
+
let request = VNRecognizeTextRequest { request, error in
|
|
42
|
+
defer { semaphore.signal() }
|
|
43
|
+
|
|
44
|
+
if let error = error {
|
|
45
|
+
let err: [String: Any] = ["error": "OCR failed: \(error.localizedDescription)"]
|
|
46
|
+
if let data = try? JSONSerialization.data(withJSONObject: err),
|
|
47
|
+
let str = String(data: data, encoding: .utf8) {
|
|
48
|
+
print(str)
|
|
49
|
+
}
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
|
|
54
|
+
|
|
55
|
+
var lineIdx = 0
|
|
56
|
+
var lines: [String] = []
|
|
57
|
+
|
|
58
|
+
for observation in observations {
|
|
59
|
+
guard let candidate = observation.topCandidates(1).first else { continue }
|
|
60
|
+
let text = candidate.string
|
|
61
|
+
let confidence = candidate.confidence
|
|
62
|
+
let box = observation.boundingBox
|
|
63
|
+
|
|
64
|
+
// Vision coordinates: origin bottom-left, normalized 0-1
|
|
65
|
+
// Convert to screen pixels: origin top-left
|
|
66
|
+
let x = box.origin.x * imageWidth
|
|
67
|
+
let y = (1.0 - box.origin.y - box.height) * imageHeight
|
|
68
|
+
let w = box.width * imageWidth
|
|
69
|
+
let h = box.height * imageHeight
|
|
70
|
+
|
|
71
|
+
// Split into words for per-word bounding boxes (approximate)
|
|
72
|
+
let words = text.components(separatedBy: " ")
|
|
73
|
+
let wordWidth = w / CGFloat(max(words.count, 1))
|
|
74
|
+
|
|
75
|
+
for (i, word) in words.enumerated() {
|
|
76
|
+
guard !word.isEmpty else { continue }
|
|
77
|
+
let element: [String: Any] = [
|
|
78
|
+
"text": word,
|
|
79
|
+
"x": Int(round(x + wordWidth * CGFloat(i))),
|
|
80
|
+
"y": Int(round(y)),
|
|
81
|
+
"width": Int(round(wordWidth)),
|
|
82
|
+
"height": Int(round(h)),
|
|
83
|
+
"confidence": round(Double(confidence) * 100) / 100,
|
|
84
|
+
"line": lineIdx
|
|
85
|
+
]
|
|
86
|
+
elements.append(element)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lines.append(text)
|
|
90
|
+
lineIdx += 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fullText = lines.joined(separator: "\n")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Configure for accuracy (fast is also available)
|
|
97
|
+
request.recognitionLevel = .accurate
|
|
98
|
+
request.usesLanguageCorrection = true
|
|
99
|
+
|
|
100
|
+
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
|
101
|
+
do {
|
|
102
|
+
try handler.perform([request])
|
|
103
|
+
} catch {
|
|
104
|
+
let err: [String: Any] = ["error": "VNImageRequestHandler failed: \(error.localizedDescription)"]
|
|
105
|
+
if let data = try? JSONSerialization.data(withJSONObject: err),
|
|
106
|
+
let str = String(data: data, encoding: .utf8) {
|
|
107
|
+
print(str)
|
|
108
|
+
}
|
|
109
|
+
exit(0)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Wait for async completion
|
|
113
|
+
semaphore.wait()
|
|
114
|
+
|
|
115
|
+
// Output JSON matching Windows format
|
|
116
|
+
let result: [String: Any] = [
|
|
117
|
+
"elements": elements,
|
|
118
|
+
"fullText": fullText
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
if let data = try? JSONSerialization.data(withJSONObject: result),
|
|
122
|
+
let str = String(data: data, encoding: .utf8) {
|
|
123
|
+
print(str)
|
|
124
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# OCR Recognition via Windows.Media.Ocr WinRT API
|
|
2
|
+
# Takes an image path, runs OCR, outputs JSON result to stdout.
|
|
3
|
+
# Called one-shot per OCR request (results cached in TypeScript layer).
|
|
4
|
+
|
|
5
|
+
param([string]$ImagePath)
|
|
6
|
+
|
|
7
|
+
# Force UTF-8 on stdout so non-ASCII recognized text (emoji, accented
|
|
8
|
+
# characters, CJK) survives the round-trip to Node. PowerShell defaults
|
|
9
|
+
# to the system code page; Node decodes as UTF-8.
|
|
10
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
11
|
+
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
# Resolve to absolute path (required by WinRT StorageFile API)
|
|
15
|
+
$ImagePath = (Resolve-Path $ImagePath -ErrorAction Stop).Path
|
|
16
|
+
|
|
17
|
+
# Load WinRT interop assembly
|
|
18
|
+
Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
|
19
|
+
|
|
20
|
+
# Load WinRT types
|
|
21
|
+
$null = [Windows.Media.Ocr.OcrEngine, Windows.Foundation, ContentType = WindowsRuntime]
|
|
22
|
+
$null = [Windows.Graphics.Imaging.SoftwareBitmap, Windows.Foundation, ContentType = WindowsRuntime]
|
|
23
|
+
$null = [Windows.Graphics.Imaging.BitmapDecoder, Windows.Foundation, ContentType = WindowsRuntime]
|
|
24
|
+
$null = [Windows.Storage.StorageFile, Windows.Foundation, ContentType = WindowsRuntime]
|
|
25
|
+
$null = [Windows.Storage.Streams.RandomAccessStream, Windows.Foundation, ContentType = WindowsRuntime]
|
|
26
|
+
|
|
27
|
+
# Find the AsTask<TResult>(IAsyncOperation<TResult>) extension method
|
|
28
|
+
$asTaskOp = [System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object {
|
|
29
|
+
$_.Name -eq 'AsTask' -and
|
|
30
|
+
$_.IsGenericMethod -and
|
|
31
|
+
$_.GetParameters().Count -eq 1 -and
|
|
32
|
+
$_.GetParameters()[0].ParameterType.IsGenericType -and
|
|
33
|
+
$_.GetParameters()[0].ParameterType.GetGenericTypeDefinition().Name -eq 'IAsyncOperation`1'
|
|
34
|
+
} | Select-Object -First 1
|
|
35
|
+
|
|
36
|
+
if (-not $asTaskOp) {
|
|
37
|
+
[Console]::Out.WriteLine('{"error":"Cannot find AsTask method for WinRT async"}')
|
|
38
|
+
[Console]::Out.Flush()
|
|
39
|
+
exit 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Invoke-Async {
|
|
43
|
+
param([object]$AsyncOp, [Type]$ResultType)
|
|
44
|
+
$genericMethod = $script:asTaskOp.MakeGenericMethod($ResultType)
|
|
45
|
+
$task = $genericMethod.Invoke($null, @($AsyncOp))
|
|
46
|
+
$task.Wait() | Out-Null
|
|
47
|
+
return $task.Result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Create OCR engine from user profile languages
|
|
51
|
+
$ocr = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages()
|
|
52
|
+
if (-not $ocr) {
|
|
53
|
+
[Console]::Out.WriteLine('{"error":"Windows OCR engine not available - no recognized languages installed"}')
|
|
54
|
+
[Console]::Out.Flush()
|
|
55
|
+
exit 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Open image via WinRT StorageFile → stream → BitmapDecoder → SoftwareBitmap
|
|
59
|
+
$storageFile = Invoke-Async ([Windows.Storage.StorageFile]::GetFileFromPathAsync($ImagePath)) ([Windows.Storage.StorageFile])
|
|
60
|
+
$stream = Invoke-Async ($storageFile.OpenAsync([Windows.Storage.FileAccessMode]::Read)) ([Windows.Storage.Streams.IRandomAccessStream])
|
|
61
|
+
$decoder = Invoke-Async ([Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($stream)) ([Windows.Graphics.Imaging.BitmapDecoder])
|
|
62
|
+
$bitmap = Invoke-Async ($decoder.GetSoftwareBitmapAsync()) ([Windows.Graphics.Imaging.SoftwareBitmap])
|
|
63
|
+
|
|
64
|
+
# Run OCR recognition
|
|
65
|
+
$ocrResult = Invoke-Async ($ocr.RecognizeAsync($bitmap)) ([Windows.Media.Ocr.OcrResult])
|
|
66
|
+
|
|
67
|
+
# Build structured result — one entry per word with bounding box
|
|
68
|
+
$elements = @()
|
|
69
|
+
$lineIdx = 0
|
|
70
|
+
foreach ($line in $ocrResult.Lines) {
|
|
71
|
+
foreach ($word in $line.Words) {
|
|
72
|
+
$r = $word.BoundingRect
|
|
73
|
+
$elements += [PSCustomObject]@{
|
|
74
|
+
text = ($word.Text -replace '[\x00-\x08\x0B\x0C\x0E-\x1F]', '')
|
|
75
|
+
x = [math]::Round($r.X)
|
|
76
|
+
y = [math]::Round($r.Y)
|
|
77
|
+
width = [math]::Round($r.Width)
|
|
78
|
+
height = [math]::Round($r.Height)
|
|
79
|
+
confidence = 1.0 # Windows.Media.Ocr does not expose per-word confidence
|
|
80
|
+
line = $lineIdx
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
$lineIdx++
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Clean up WinRT resources
|
|
87
|
+
try { $stream.Dispose() } catch {}
|
|
88
|
+
try { $bitmap.Dispose() } catch {}
|
|
89
|
+
|
|
90
|
+
# Output single-line JSON
|
|
91
|
+
$output = [PSCustomObject]@{
|
|
92
|
+
elements = @($elements) # force array even if 0 or 1 element
|
|
93
|
+
fullText = if ($ocrResult.Text) { $ocrResult.Text } else { "" }
|
|
94
|
+
}
|
|
95
|
+
[Console]::Out.WriteLine(($output | ConvertTo-Json -Depth 3 -Compress))
|
|
96
|
+
[Console]::Out.Flush()
|
|
97
|
+
|
|
98
|
+
} catch {
|
|
99
|
+
[Console]::Out.WriteLine((@{ error = $_.Exception.Message } | ConvertTo-Json -Compress))
|
|
100
|
+
[Console]::Out.Flush()
|
|
101
|
+
exit 0
|
|
102
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* macOS-only postinstall step (issue #149).
|
|
4
|
+
*
|
|
5
|
+
* The published npm package ships the Swift SOURCES for the native helper
|
|
6
|
+
* but not a prebuilt binary. Without the binary, screenshots / display
|
|
7
|
+
* enumeration / TCC permission checks all fail. This script builds the
|
|
8
|
+
* helper from the shipped sources on a fresh `npm i -g clawdcursor` so a
|
|
9
|
+
* Mac user gets a working install out of the box.
|
|
10
|
+
*
|
|
11
|
+
* Best-effort by design: it NEVER fails the install. If the Xcode toolchain
|
|
12
|
+
* (`swift`) is absent, it prints actionable guidance and exits 0 — the
|
|
13
|
+
* runtime emits the same guidance the first time a screenshot is needed.
|
|
14
|
+
*
|
|
15
|
+
* No-op on Windows/Linux.
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
const { execFileSync, execSync } = require('child_process');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
|
|
22
|
+
if (process.platform !== 'darwin') process.exit(0);
|
|
23
|
+
|
|
24
|
+
const root = path.join(__dirname, '..');
|
|
25
|
+
const nativeDir = path.join(root, 'native');
|
|
26
|
+
const buildSh = path.join(nativeDir, 'build.sh');
|
|
27
|
+
|
|
28
|
+
// Dev checkouts / odd layouts: if sources aren't present, there's nothing to do.
|
|
29
|
+
if (!fs.existsSync(buildSh)) process.exit(0);
|
|
30
|
+
|
|
31
|
+
// Is the Swift toolchain available?
|
|
32
|
+
try {
|
|
33
|
+
execSync('command -v swift', { stdio: 'ignore', shell: '/bin/bash' });
|
|
34
|
+
} catch {
|
|
35
|
+
console.log('[clawdcursor] Native macOS helper not built — Xcode Command Line Tools (swift) not found.');
|
|
36
|
+
console.log(' Screenshots, display enumeration, and permission checks need it.');
|
|
37
|
+
console.log(' Install the tools, then build: xcode-select --install && cd "' + nativeDir + '" && ./build.sh');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
console.log('[clawdcursor] Building native macOS helper from source (native/build.sh)…');
|
|
43
|
+
execFileSync('/bin/bash', [buildSh], { stdio: 'inherit', cwd: nativeDir });
|
|
44
|
+
console.log('[clawdcursor] Native macOS helper built.');
|
|
45
|
+
} catch (_e) {
|
|
46
|
+
console.log('[clawdcursor] Native macOS helper build failed (non-fatal). Build manually with: cd native && ./build.sh');
|
|
47
|
+
}
|
|
48
|
+
process.exit(0);
|