@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,197 @@
|
|
|
1
|
+
#!/usr/bin/env osascript -l JavaScript
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* get-screen-context.jxa
|
|
5
|
+
*
|
|
6
|
+
* Combined script: gets windows list + focused window UI tree in ONE osascript spawn.
|
|
7
|
+
* macOS equivalent of get-screen-context.ps1.
|
|
8
|
+
*
|
|
9
|
+
* Output shape (matches PS1 exactly so accessibility.ts needs no changes):
|
|
10
|
+
* { windows: WindowInfo[], uiTree: UINode | null }
|
|
11
|
+
*
|
|
12
|
+
* Parameters (read from argv):
|
|
13
|
+
* -FocusedProcessId <number> — if provided, include UI tree for that process
|
|
14
|
+
* -MaxDepth <number> — max UI tree depth (default 2)
|
|
15
|
+
*
|
|
16
|
+
* Output: script result (last expression) goes to stdout for Node.js execFile.
|
|
17
|
+
*
|
|
18
|
+
* Usage: osascript -l JavaScript get-screen-context.jxa -FocusedProcessId 1234 -MaxDepth 2
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
ObjC.import('stdlib');
|
|
22
|
+
|
|
23
|
+
// ─── Argument parsing ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function parseArgs() {
|
|
26
|
+
var args = ObjC.unwrap($.NSProcessInfo.processInfo.arguments);
|
|
27
|
+
var result = { focusedProcessId: 0, maxDepth: 2 };
|
|
28
|
+
for (var i = 0; i < args.length; i++) {
|
|
29
|
+
var arg = ObjC.unwrap(args[i]);
|
|
30
|
+
if (arg === '-FocusedProcessId' && i + 1 < args.length) {
|
|
31
|
+
result.focusedProcessId = parseInt(ObjC.unwrap(args[i + 1]), 10) || 0;
|
|
32
|
+
} else if (arg === '-MaxDepth' && i + 1 < args.length) {
|
|
33
|
+
result.maxDepth = parseInt(ObjC.unwrap(args[i + 1]), 10) || 2;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function safeGet(obj, prop, def) {
|
|
42
|
+
try { var v = obj[prop](); return (v !== undefined && v !== null) ? v : def; }
|
|
43
|
+
catch (e) { return def; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeProp(el, prop, def) {
|
|
47
|
+
try { return el[prop] ? el[prop]() : def; }
|
|
48
|
+
catch (e) { return def; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getBounds(el) {
|
|
52
|
+
try {
|
|
53
|
+
var pos = el.position();
|
|
54
|
+
var sz = el.size();
|
|
55
|
+
return { x: pos[0], y: pos[1], width: sz[0], height: sz[1] };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Part 1: windows list (same logic as get-windows.jxa) ────────────────────
|
|
62
|
+
|
|
63
|
+
function getWindows(SystemEvents) {
|
|
64
|
+
var processes = SystemEvents.processes.where({ backgroundOnly: false });
|
|
65
|
+
var results = [];
|
|
66
|
+
|
|
67
|
+
for (var i = 0; i < processes.length; i++) {
|
|
68
|
+
try {
|
|
69
|
+
var proc = processes[i];
|
|
70
|
+
var procName = safeGet(proc, 'name', 'unknown');
|
|
71
|
+
var procId = safeGet(proc, 'unixId', 0);
|
|
72
|
+
var windows = proc.windows;
|
|
73
|
+
|
|
74
|
+
for (var j = 0; j < windows.length; j++) {
|
|
75
|
+
try {
|
|
76
|
+
var win = windows[j];
|
|
77
|
+
var title = safeProp(win, 'name', '');
|
|
78
|
+
if (!title || title.trim().length === 0) continue;
|
|
79
|
+
|
|
80
|
+
var isMinimized = false;
|
|
81
|
+
try { isMinimized = win.miniaturized(); } catch (e) {}
|
|
82
|
+
|
|
83
|
+
results.push({
|
|
84
|
+
handle: procId,
|
|
85
|
+
title: title,
|
|
86
|
+
processName: procName,
|
|
87
|
+
processId: procId,
|
|
88
|
+
bounds: getBounds(win),
|
|
89
|
+
isMinimized: isMinimized
|
|
90
|
+
});
|
|
91
|
+
} catch (e) {}
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Part 2: UI tree for focused process ─────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
// Interactive macOS roles worth sending to the LLM
|
|
102
|
+
var INTERACTIVE_ROLES = {
|
|
103
|
+
'AXButton': true, 'AXTextField': true, 'AXTextArea': true,
|
|
104
|
+
'AXComboBox': true, 'AXCheckBox': true, 'AXRadioButton': true,
|
|
105
|
+
'AXLink': true, 'AXMenuItem': true, 'AXMenu': true,
|
|
106
|
+
'AXTab': true, 'AXTabGroup': true, 'AXList': true,
|
|
107
|
+
'AXOutline': true, 'AXSlider': true, 'AXScrollBar': true,
|
|
108
|
+
'AXToolbar': true, 'AXWebArea': true, 'AXStaticText': true,
|
|
109
|
+
'AXGroup': true
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function buildUINode(element, maxDepth, depth) {
|
|
113
|
+
if (depth > maxDepth) return null;
|
|
114
|
+
|
|
115
|
+
var name = '';
|
|
116
|
+
var role = '';
|
|
117
|
+
var value = '';
|
|
118
|
+
var subrole = '';
|
|
119
|
+
|
|
120
|
+
try { name = element.name() || ''; } catch (e) {}
|
|
121
|
+
try { role = element.role() || ''; } catch (e) {}
|
|
122
|
+
try { value = element.value() || ''; } catch (e) {}
|
|
123
|
+
// Secureness lives in the AX SUBROLE: a password field reports
|
|
124
|
+
// role=AXTextField, subrole=AXSecureTextField. Surface it so the snapshot
|
|
125
|
+
// builder can redact the value (audit 2026-06-11, M2) — without this the
|
|
126
|
+
// cleartext password reached the LLM prompt + fingerprint on macOS.
|
|
127
|
+
try { subrole = element.subrole() || ''; } catch (e) {}
|
|
128
|
+
|
|
129
|
+
var isInteractive = !!INTERACTIVE_ROLES[role];
|
|
130
|
+
var hasName = name.trim().length > 0;
|
|
131
|
+
var isSecure = (subrole === 'AXSecureTextField');
|
|
132
|
+
if (isSecure) { value = ''; } // never emit the secret, even pre-redaction
|
|
133
|
+
|
|
134
|
+
// Build children first so we can decide whether to include this node
|
|
135
|
+
var children = [];
|
|
136
|
+
if (depth < maxDepth) {
|
|
137
|
+
try {
|
|
138
|
+
var kids = element.uiElements();
|
|
139
|
+
for (var i = 0; i < kids.length; i++) {
|
|
140
|
+
try {
|
|
141
|
+
var child = buildUINode(kids[i], maxDepth, depth + 1);
|
|
142
|
+
if (child) children.push(child);
|
|
143
|
+
} catch (e) {}
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Skip uninteresting, unnamed leaf nodes (mirrors PS1 behaviour)
|
|
149
|
+
if (!isInteractive && !hasName && children.length === 0) return null;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
name: name,
|
|
153
|
+
automationId: '', // macOS has no AutomationId concept
|
|
154
|
+
controlType: role,
|
|
155
|
+
subrole: subrole || undefined,
|
|
156
|
+
secure: isSecure || undefined,
|
|
157
|
+
bounds: getBounds(element),
|
|
158
|
+
value: value || undefined,
|
|
159
|
+
children: children.length > 0 ? children : undefined
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getUITree(SystemEvents, focusedProcessId, maxDepth) {
|
|
164
|
+
if (!focusedProcessId) return null;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
var procs = SystemEvents.processes.where({ unixId: focusedProcessId });
|
|
168
|
+
if (procs.length === 0) return null;
|
|
169
|
+
|
|
170
|
+
var proc = procs[0];
|
|
171
|
+
var windows = proc.windows;
|
|
172
|
+
if (windows.length === 0) return null;
|
|
173
|
+
|
|
174
|
+
// Use the first (frontmost) window
|
|
175
|
+
return buildUINode(windows[0], maxDepth, 0);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
var output;
|
|
184
|
+
try {
|
|
185
|
+
var params = parseArgs();
|
|
186
|
+
var SystemEvents = Application('System Events');
|
|
187
|
+
SystemEvents.includeStandardAdditions = true;
|
|
188
|
+
|
|
189
|
+
var windows = getWindows(SystemEvents);
|
|
190
|
+
var uiTree = getUITree(SystemEvents, params.focusedProcessId, params.maxDepth);
|
|
191
|
+
|
|
192
|
+
output = JSON.stringify({ windows: windows, uiTree: uiTree });
|
|
193
|
+
} catch (error) {
|
|
194
|
+
output = JSON.stringify({ error: error.toString() });
|
|
195
|
+
$.exit(1);
|
|
196
|
+
}
|
|
197
|
+
output;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# get-ui-tree.sh — Get the accessibility tree of the frontmost app on macOS
|
|
3
|
+
#
|
|
4
|
+
# Uses JXA (JavaScript for Automation) + System Events to walk the UI element
|
|
5
|
+
# hierarchy of the frontmost application.
|
|
6
|
+
#
|
|
7
|
+
# Returns JSON with name, role, subrole, description, position, size for each element.
|
|
8
|
+
#
|
|
9
|
+
# Parameters:
|
|
10
|
+
# -ProcessId <number> Optional. If specified, get tree for this process.
|
|
11
|
+
# Otherwise uses the frontmost app.
|
|
12
|
+
# -MaxDepth <number> Optional. Maximum traversal depth (default 6).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# chmod +x scripts/mac/get-ui-tree.sh
|
|
16
|
+
# ./scripts/mac/get-ui-tree.sh
|
|
17
|
+
# ./scripts/mac/get-ui-tree.sh -ProcessId 1234
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
# Parse arguments
|
|
22
|
+
PROCESS_ID=""
|
|
23
|
+
MAX_DEPTH="6"
|
|
24
|
+
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
-ProcessId) PROCESS_ID="$2"; shift 2 ;;
|
|
28
|
+
-MaxDepth) MAX_DEPTH="$2"; shift 2 ;;
|
|
29
|
+
*) shift ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
# Run JXA inline to walk the accessibility tree
|
|
34
|
+
osascript -l JavaScript - "$PROCESS_ID" "$MAX_DEPTH" <<'ENDSCRIPT'
|
|
35
|
+
ObjC.import('stdlib');
|
|
36
|
+
|
|
37
|
+
var args = ObjC.unwrap($.NSProcessInfo.processInfo.arguments);
|
|
38
|
+
// Arguments: [osascript, -, processId, maxDepth]
|
|
39
|
+
var targetPid = args.length > 2 ? parseInt(ObjC.unwrap(args[2]), 10) : 0;
|
|
40
|
+
var maxDepth = args.length > 3 ? parseInt(ObjC.unwrap(args[3]), 10) : 6;
|
|
41
|
+
|
|
42
|
+
function safeGet(obj, property, defaultValue) {
|
|
43
|
+
try {
|
|
44
|
+
var val = obj[property]();
|
|
45
|
+
return val !== undefined && val !== null ? val : defaultValue;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return defaultValue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function walkTree(element, depth) {
|
|
52
|
+
if (depth > maxDepth) return null;
|
|
53
|
+
|
|
54
|
+
var node = {};
|
|
55
|
+
|
|
56
|
+
try { node.name = element.name() || ''; } catch (e) { node.name = ''; }
|
|
57
|
+
try { node.role = element.role() || ''; } catch (e) { node.role = ''; }
|
|
58
|
+
try { node.subrole = element.subrole() || ''; } catch (e) { node.subrole = ''; }
|
|
59
|
+
try { node.description = element.description() || ''; } catch (e) { node.description = ''; }
|
|
60
|
+
try { node.value = String(element.value() || ''); } catch (e) { node.value = ''; }
|
|
61
|
+
try { node.enabled = element.enabled(); } catch (e) { node.enabled = true; }
|
|
62
|
+
|
|
63
|
+
// Position and size
|
|
64
|
+
try {
|
|
65
|
+
var pos = element.position();
|
|
66
|
+
var sz = element.size();
|
|
67
|
+
node.position = { x: pos[0] || 0, y: pos[1] || 0 };
|
|
68
|
+
node.size = { width: sz[0] || 0, height: sz[1] || 0 };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
node.position = { x: 0, y: 0 };
|
|
71
|
+
node.size = { width: 0, height: 0 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Recurse into children
|
|
75
|
+
var children = [];
|
|
76
|
+
try {
|
|
77
|
+
var uiElements = element.uiElements();
|
|
78
|
+
for (var i = 0; i < uiElements.length && i < 50; i++) {
|
|
79
|
+
var child = walkTree(uiElements[i], depth + 1);
|
|
80
|
+
if (child) children.push(child);
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// No children accessible
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (children.length > 0) {
|
|
87
|
+
node.children = children;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return node;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
var SystemEvents = Application('System Events');
|
|
95
|
+
SystemEvents.includeStandardAdditions = true;
|
|
96
|
+
|
|
97
|
+
var targetProcess;
|
|
98
|
+
if (targetPid && targetPid > 0) {
|
|
99
|
+
var procs = SystemEvents.processes.where({ unixId: targetPid });
|
|
100
|
+
if (procs.length === 0) {
|
|
101
|
+
console.log(JSON.stringify({ error: 'No process found with ID ' + targetPid }));
|
|
102
|
+
$.exit(1);
|
|
103
|
+
}
|
|
104
|
+
targetProcess = procs[0];
|
|
105
|
+
} else {
|
|
106
|
+
// Use frontmost process
|
|
107
|
+
var frontProcs = SystemEvents.processes.where({ frontmost: true });
|
|
108
|
+
if (frontProcs.length === 0) {
|
|
109
|
+
console.log(JSON.stringify({ error: 'No frontmost process found' }));
|
|
110
|
+
$.exit(1);
|
|
111
|
+
}
|
|
112
|
+
targetProcess = frontProcs[0];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var processName = safeGet(targetProcess, 'name', 'unknown');
|
|
116
|
+
var processId = safeGet(targetProcess, 'unixId', 0);
|
|
117
|
+
|
|
118
|
+
var tree = {
|
|
119
|
+
processName: processName,
|
|
120
|
+
processId: processId,
|
|
121
|
+
windows: []
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
var windows = targetProcess.windows;
|
|
125
|
+
for (var w = 0; w < windows.length && w < 5; w++) {
|
|
126
|
+
try {
|
|
127
|
+
var winNode = walkTree(windows[w], 0);
|
|
128
|
+
if (winNode) {
|
|
129
|
+
tree.windows.push(winNode);
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// Window not accessible
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(JSON.stringify(tree, null, 0));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(JSON.stringify({ error: error.toString() }));
|
|
139
|
+
$.exit(1);
|
|
140
|
+
}
|
|
141
|
+
ENDSCRIPT
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env osascript -l JavaScript
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* get-windows.jxa
|
|
5
|
+
*
|
|
6
|
+
* Lists all visible top-level windows with their properties.
|
|
7
|
+
* Returns a JSON array of all visible windows including:
|
|
8
|
+
* - title: Window title (name)
|
|
9
|
+
* - processName: Name of the owning application
|
|
10
|
+
* - processId: Unix process ID
|
|
11
|
+
* - bounds: {x, y, width, height} window position and size
|
|
12
|
+
* - isMinimized: Boolean indicating if window is minimized
|
|
13
|
+
*
|
|
14
|
+
* Usage: osascript -l JavaScript get-windows.jxa
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Utility function to safely get property values
|
|
18
|
+
function safeGet(obj, property, defaultValue) {
|
|
19
|
+
try {
|
|
20
|
+
var val = obj[property]();
|
|
21
|
+
return val !== undefined && val !== null ? val : defaultValue;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return defaultValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Utility function to safely get UI element properties
|
|
28
|
+
function safeElementProp(element, propName, defaultValue) {
|
|
29
|
+
try {
|
|
30
|
+
if (element[propName]) {
|
|
31
|
+
return element[propName]();
|
|
32
|
+
}
|
|
33
|
+
return defaultValue;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return defaultValue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// run(argv) is the JXA entry point — osascript calls it and its return value becomes stdout.
|
|
40
|
+
function run(argv) {
|
|
41
|
+
try {
|
|
42
|
+
// Get System Events application
|
|
43
|
+
var SystemEvents = Application('System Events');
|
|
44
|
+
SystemEvents.includeStandardAdditions = true;
|
|
45
|
+
|
|
46
|
+
// Get all processes with UI
|
|
47
|
+
var processes = SystemEvents.processes.where({ backgroundOnly: false });
|
|
48
|
+
var results = [];
|
|
49
|
+
|
|
50
|
+
for (var i = 0; i < processes.length; i++) {
|
|
51
|
+
try {
|
|
52
|
+
var process = processes[i];
|
|
53
|
+
var processName = safeGet(process, 'name', 'unknown');
|
|
54
|
+
var processId = safeGet(process, 'unixId', 0);
|
|
55
|
+
|
|
56
|
+
// Get all windows for this process
|
|
57
|
+
var windows = process.windows;
|
|
58
|
+
|
|
59
|
+
for (var j = 0; j < windows.length; j++) {
|
|
60
|
+
try {
|
|
61
|
+
var win = windows[j];
|
|
62
|
+
var title = safeElementProp(win, 'name', '');
|
|
63
|
+
|
|
64
|
+
// Skip windows with no name (invisible/system windows)
|
|
65
|
+
if (!title || title.trim().length === 0) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get window bounds
|
|
70
|
+
var bounds = { x: 0, y: 0, width: 0, height: 0 };
|
|
71
|
+
try {
|
|
72
|
+
var position = win.position();
|
|
73
|
+
var size = win.size();
|
|
74
|
+
bounds = {
|
|
75
|
+
x: position[0],
|
|
76
|
+
y: position[1],
|
|
77
|
+
width: size[0],
|
|
78
|
+
height: size[1]
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Bounds not available
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if minimized - macOS uses "miniaturized"
|
|
85
|
+
var isMinimized = false;
|
|
86
|
+
try {
|
|
87
|
+
isMinimized = win.miniaturized();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Property not available, assume not minimized
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// macOS doesn't have window handles like Windows,
|
|
93
|
+
// but we can use a combination of processId and index
|
|
94
|
+
results.push({
|
|
95
|
+
title: title,
|
|
96
|
+
processName: processName,
|
|
97
|
+
processId: processId,
|
|
98
|
+
bounds: bounds,
|
|
99
|
+
isMinimized: isMinimized
|
|
100
|
+
});
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Skip windows that throw on property access
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// Skip processes that throw
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Return JSON — osascript captures the return value as stdout
|
|
111
|
+
return JSON.stringify(results);
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Return error as JSON
|
|
115
|
+
return JSON.stringify({ error: error.toString() });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# interact-element.sh — Click, type, set value, focus elements on macOS
|
|
3
|
+
#
|
|
4
|
+
# Uses JXA (JavaScript for Automation) + System Events to interact with
|
|
5
|
+
# native macOS UI elements via the Accessibility API.
|
|
6
|
+
#
|
|
7
|
+
# Outputs JSON matching the Windows invoke-element.ps1 / interact-element.ps1 format.
|
|
8
|
+
#
|
|
9
|
+
# Parameters:
|
|
10
|
+
# -Name <string> Match elements by name (contains, case-insensitive)
|
|
11
|
+
# -AutomationId <string> Match by description/identifier
|
|
12
|
+
# -ControlType <string> Filter by control type (Button, Edit, etc.)
|
|
13
|
+
# -ProcessId <number> Required. Process ID of the target application
|
|
14
|
+
# -Action <string> Required. Action: click, set-value, get-value, focus,
|
|
15
|
+
# toggle, expand, collapse, select, sendkeys
|
|
16
|
+
# -Value <string> Value for set-value/sendkeys actions
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# chmod +x scripts/mac/interact-element.sh
|
|
20
|
+
# ./scripts/mac/interact-element.sh -Name "Save" -Action click -ProcessId 1234
|
|
21
|
+
# ./scripts/mac/interact-element.sh -Name "Search" -Action set-value -Value "hello" -ProcessId 1234
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
|
|
27
|
+
# Parse arguments
|
|
28
|
+
NAME=""
|
|
29
|
+
AUTOMATION_ID=""
|
|
30
|
+
CONTROL_TYPE=""
|
|
31
|
+
PROCESS_ID=""
|
|
32
|
+
ACTION=""
|
|
33
|
+
VALUE=""
|
|
34
|
+
|
|
35
|
+
while [[ $# -gt 0 ]]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
-Name) NAME="$2"; shift 2 ;;
|
|
38
|
+
-AutomationId) AUTOMATION_ID="$2"; shift 2 ;;
|
|
39
|
+
-ControlType) CONTROL_TYPE="$2"; shift 2 ;;
|
|
40
|
+
-ProcessId) PROCESS_ID="$2"; shift 2 ;;
|
|
41
|
+
-Action) ACTION="$2"; shift 2 ;;
|
|
42
|
+
-Value) VALUE="$2"; shift 2 ;;
|
|
43
|
+
*) shift ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
# Validate required parameters
|
|
48
|
+
if [[ -z "$ACTION" ]]; then
|
|
49
|
+
echo '{"success":false,"error":"Missing required parameter: -Action"}'
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [[ -z "$PROCESS_ID" || "$PROCESS_ID" == "0" ]]; then
|
|
54
|
+
echo '{"success":false,"error":"Missing required parameter: -ProcessId"}'
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Map Windows ControlType to macOS role
|
|
59
|
+
map_control_type() {
|
|
60
|
+
local ct="$1"
|
|
61
|
+
case "$ct" in
|
|
62
|
+
Button) echo "button" ;;
|
|
63
|
+
Edit) echo "text field" ;;
|
|
64
|
+
Text) echo "static text" ;;
|
|
65
|
+
CheckBox) echo "checkbox" ;;
|
|
66
|
+
RadioButton) echo "radio button" ;;
|
|
67
|
+
ComboBox) echo "pop up button" ;;
|
|
68
|
+
MenuItem) echo "menu item" ;;
|
|
69
|
+
Menu) echo "menu" ;;
|
|
70
|
+
List) echo "list" ;;
|
|
71
|
+
ListItem) echo "row" ;;
|
|
72
|
+
Tree) echo "outline" ;;
|
|
73
|
+
TreeItem) echo "row" ;;
|
|
74
|
+
Tab) echo "tab group" ;;
|
|
75
|
+
TabItem) echo "radio button" ;;
|
|
76
|
+
Window) echo "window" ;;
|
|
77
|
+
*) echo "" ;;
|
|
78
|
+
esac
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Determine search name (use Name or AutomationId)
|
|
82
|
+
SEARCH_NAME="${NAME:-$AUTOMATION_ID}"
|
|
83
|
+
|
|
84
|
+
# For actions that can use the existing invoke-element.jxa
|
|
85
|
+
case "$ACTION" in
|
|
86
|
+
click|set-value|get-value|focus)
|
|
87
|
+
# Build JXA arguments
|
|
88
|
+
JXA_ARGS=("-action" "$ACTION" "-processId" "$PROCESS_ID")
|
|
89
|
+
|
|
90
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
91
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [[ -n "$CONTROL_TYPE" ]]; then
|
|
95
|
+
MAPPED_ROLE=$(map_control_type "$CONTROL_TYPE")
|
|
96
|
+
if [[ -n "$MAPPED_ROLE" ]]; then
|
|
97
|
+
JXA_ARGS+=("-role" "$MAPPED_ROLE")
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [[ -n "$VALUE" ]]; then
|
|
102
|
+
JXA_ARGS+=("-value" "$VALUE")
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Run invoke-element.jxa
|
|
106
|
+
osascript -l JavaScript "$SCRIPT_DIR/invoke-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || \
|
|
107
|
+
echo '{"success":false,"error":"JXA script failed"}'
|
|
108
|
+
;;
|
|
109
|
+
|
|
110
|
+
sendkeys)
|
|
111
|
+
# Focus the element, then use System Events keystroke
|
|
112
|
+
if [[ -z "$VALUE" ]]; then
|
|
113
|
+
echo '{"success":false,"error":"Value parameter required for sendkeys action"}'
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# First focus the element
|
|
118
|
+
JXA_ARGS=("-action" "focus" "-processId" "$PROCESS_ID")
|
|
119
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
120
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
FOCUS_RESULT=$(osascript -l JavaScript "$SCRIPT_DIR/invoke-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || echo '{"success":false}')
|
|
124
|
+
|
|
125
|
+
# Check if focus succeeded
|
|
126
|
+
FOCUS_OK=$(echo "$FOCUS_RESULT" | /usr/bin/python3 -c "import sys,json; d=json.load(sys.stdin); print('true' if d.get('success') else 'false')" 2>/dev/null || echo "false")
|
|
127
|
+
|
|
128
|
+
if [[ "$FOCUS_OK" != "true" ]]; then
|
|
129
|
+
echo '{"success":false,"error":"Failed to focus element for sendkeys"}'
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Small delay for focus to take effect
|
|
134
|
+
sleep 0.1
|
|
135
|
+
|
|
136
|
+
# Send keystrokes via System Events (sanitize input via python3)
|
|
137
|
+
SAFE_VALUE=$(/usr/bin/python3 -c "import sys,json; print(json.dumps(sys.argv[1]))" "$VALUE" 2>/dev/null)
|
|
138
|
+
# SAFE_VALUE is now a JSON-quoted string like "hello \"world\""
|
|
139
|
+
# Strip outer quotes for AppleScript
|
|
140
|
+
SAFE_VALUE=${SAFE_VALUE:1:-1}
|
|
141
|
+
osascript -e "
|
|
142
|
+
tell application \"System Events\"
|
|
143
|
+
keystroke \"${SAFE_VALUE}\"
|
|
144
|
+
end tell
|
|
145
|
+
" 2>/dev/null
|
|
146
|
+
|
|
147
|
+
if [[ $? -eq 0 ]]; then
|
|
148
|
+
echo "{\"success\":true,\"action\":\"sendkeys\",\"method\":\"keystroke\",\"value\":$(echo "$VALUE" | /usr/bin/python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))')}"
|
|
149
|
+
else
|
|
150
|
+
echo '{"success":false,"error":"keystroke failed"}'
|
|
151
|
+
fi
|
|
152
|
+
;;
|
|
153
|
+
|
|
154
|
+
toggle)
|
|
155
|
+
# Toggle a checkbox: click it to toggle state
|
|
156
|
+
JXA_ARGS=("-action" "click" "-processId" "$PROCESS_ID")
|
|
157
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
158
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
159
|
+
fi
|
|
160
|
+
if [[ -n "$CONTROL_TYPE" ]]; then
|
|
161
|
+
MAPPED_ROLE=$(map_control_type "$CONTROL_TYPE")
|
|
162
|
+
if [[ -n "$MAPPED_ROLE" ]]; then
|
|
163
|
+
JXA_ARGS+=("-role" "$MAPPED_ROLE")
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
RESULT=$(osascript -l JavaScript "$SCRIPT_DIR/invoke-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || echo '{"success":false}')
|
|
168
|
+
|
|
169
|
+
# Rewrite the result to indicate toggle action
|
|
170
|
+
echo "$RESULT" | /usr/bin/python3 -c "
|
|
171
|
+
import sys, json
|
|
172
|
+
try:
|
|
173
|
+
d = json.load(sys.stdin)
|
|
174
|
+
d['action'] = 'toggle'
|
|
175
|
+
if d.get('success'):
|
|
176
|
+
d['method'] = 'click-toggle'
|
|
177
|
+
print(json.dumps(d, separators=(',', ':')))
|
|
178
|
+
except:
|
|
179
|
+
print('{\"success\":false,\"error\":\"toggle failed\"}')" 2>/dev/null || echo '{"success":false,"error":"toggle failed"}'
|
|
180
|
+
;;
|
|
181
|
+
|
|
182
|
+
expand|collapse)
|
|
183
|
+
# macOS doesn't have ExpandCollapsePattern directly.
|
|
184
|
+
# Try AXPress action (works for disclosure triangles) or click
|
|
185
|
+
JXA_ARGS=("-action" "click" "-processId" "$PROCESS_ID")
|
|
186
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
187
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
RESULT=$(osascript -l JavaScript "$SCRIPT_DIR/invoke-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || echo '{"success":false}')
|
|
191
|
+
|
|
192
|
+
echo "$RESULT" | /usr/bin/python3 -c "
|
|
193
|
+
import sys, json
|
|
194
|
+
try:
|
|
195
|
+
d = json.load(sys.stdin)
|
|
196
|
+
d['action'] = '${ACTION}'
|
|
197
|
+
if d.get('success'):
|
|
198
|
+
d['method'] = 'click-${ACTION}'
|
|
199
|
+
print(json.dumps(d, separators=(',', ':')))
|
|
200
|
+
except:
|
|
201
|
+
print('{\"success\":false,\"error\":\"${ACTION} failed\"}')" 2>/dev/null || echo "{\"success\":false,\"error\":\"${ACTION} failed\"}"
|
|
202
|
+
;;
|
|
203
|
+
|
|
204
|
+
select)
|
|
205
|
+
# Select: click the item (works for list items, tabs, radio buttons)
|
|
206
|
+
JXA_ARGS=("-action" "click" "-processId" "$PROCESS_ID")
|
|
207
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
208
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
209
|
+
fi
|
|
210
|
+
if [[ -n "$CONTROL_TYPE" ]]; then
|
|
211
|
+
MAPPED_ROLE=$(map_control_type "$CONTROL_TYPE")
|
|
212
|
+
if [[ -n "$MAPPED_ROLE" ]]; then
|
|
213
|
+
JXA_ARGS+=("-role" "$MAPPED_ROLE")
|
|
214
|
+
fi
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
RESULT=$(osascript -l JavaScript "$SCRIPT_DIR/invoke-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || echo '{"success":false}')
|
|
218
|
+
|
|
219
|
+
echo "$RESULT" | /usr/bin/python3 -c "
|
|
220
|
+
import sys, json
|
|
221
|
+
try:
|
|
222
|
+
d = json.load(sys.stdin)
|
|
223
|
+
d['action'] = 'select'
|
|
224
|
+
if d.get('success'):
|
|
225
|
+
d['method'] = 'click-select'
|
|
226
|
+
print(json.dumps(d, separators=(',', ':')))
|
|
227
|
+
except:
|
|
228
|
+
print('{\"success\":false,\"error\":\"select failed\"}')" 2>/dev/null || echo '{"success":false,"error":"select failed"}'
|
|
229
|
+
;;
|
|
230
|
+
|
|
231
|
+
*)
|
|
232
|
+
echo "{\"success\":false,\"error\":\"Unknown action: $ACTION\"}"
|
|
233
|
+
exit 0
|
|
234
|
+
;;
|
|
235
|
+
esac
|