@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,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Linux OCR via Tesseract (pytesseract) or tesseract CLI.
|
|
4
|
+
Takes an image path, outputs JSON result to stdout.
|
|
5
|
+
Matches the same JSON format as ocr-recognize.ps1 (Windows).
|
|
6
|
+
|
|
7
|
+
Usage: python3 ocr-recognize.py /path/to/image.png
|
|
8
|
+
|
|
9
|
+
Requires: tesseract-ocr package
|
|
10
|
+
Ubuntu/Debian: sudo apt install tesseract-ocr
|
|
11
|
+
Fedora: sudo dnf install tesseract
|
|
12
|
+
Arch: sudo pacman -S tesseract
|
|
13
|
+
|
|
14
|
+
Optional: pip install pytesseract (for bounding boxes)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
|
|
23
|
+
def ocr_with_tesseract_cli(image_path):
|
|
24
|
+
"""Use tesseract CLI with TSV output for bounding boxes."""
|
|
25
|
+
try:
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
['tesseract', image_path, '-', 'tsv'],
|
|
28
|
+
capture_output=True, text=True, timeout=30
|
|
29
|
+
)
|
|
30
|
+
if result.returncode != 0:
|
|
31
|
+
return {"error": f"tesseract failed: {result.stderr.strip()}"}
|
|
32
|
+
|
|
33
|
+
elements = []
|
|
34
|
+
lines_text = []
|
|
35
|
+
current_line = -1
|
|
36
|
+
|
|
37
|
+
for line in result.stdout.strip().split('\n')[1:]: # skip header
|
|
38
|
+
parts = line.split('\t')
|
|
39
|
+
if len(parts) < 12:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
level, page, block, par, line_num, word_num = parts[:6]
|
|
43
|
+
left, top, width, height = parts[6:10]
|
|
44
|
+
conf = parts[10]
|
|
45
|
+
text = parts[11].strip() if len(parts) > 11 else ''
|
|
46
|
+
|
|
47
|
+
if not text or conf == '-1':
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
line_idx = int(line_num)
|
|
51
|
+
if line_idx != current_line:
|
|
52
|
+
current_line = line_idx
|
|
53
|
+
if text:
|
|
54
|
+
lines_text.append(text)
|
|
55
|
+
else:
|
|
56
|
+
lines_text.append('')
|
|
57
|
+
else:
|
|
58
|
+
if lines_text:
|
|
59
|
+
lines_text[-1] += ' ' + text
|
|
60
|
+
|
|
61
|
+
elements.append({
|
|
62
|
+
"text": text,
|
|
63
|
+
"x": int(left),
|
|
64
|
+
"y": int(top),
|
|
65
|
+
"width": int(width),
|
|
66
|
+
"height": int(height),
|
|
67
|
+
"confidence": round(max(0, int(conf)) / 100, 2),
|
|
68
|
+
"line": line_idx
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"elements": elements,
|
|
73
|
+
"fullText": '\n'.join(lines_text)
|
|
74
|
+
}
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
return {"error": "tesseract not found. Install: sudo apt install tesseract-ocr"}
|
|
77
|
+
except subprocess.TimeoutExpired:
|
|
78
|
+
return {"error": "tesseract timed out after 30s"}
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return {"error": f"tesseract error: {str(e)}"}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def ocr_with_pytesseract(image_path):
|
|
84
|
+
"""Use pytesseract for bounding boxes (if installed)."""
|
|
85
|
+
try:
|
|
86
|
+
import pytesseract
|
|
87
|
+
from PIL import Image
|
|
88
|
+
|
|
89
|
+
img = Image.open(image_path)
|
|
90
|
+
data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
|
|
91
|
+
|
|
92
|
+
elements = []
|
|
93
|
+
lines_text = []
|
|
94
|
+
current_line = -1
|
|
95
|
+
|
|
96
|
+
for i in range(len(data['text'])):
|
|
97
|
+
text = data['text'][i].strip()
|
|
98
|
+
conf = int(data['conf'][i])
|
|
99
|
+
|
|
100
|
+
if not text or conf < 0:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
line_idx = data['line_num'][i]
|
|
104
|
+
if line_idx != current_line:
|
|
105
|
+
current_line = line_idx
|
|
106
|
+
lines_text.append(text)
|
|
107
|
+
else:
|
|
108
|
+
if lines_text:
|
|
109
|
+
lines_text[-1] += ' ' + text
|
|
110
|
+
|
|
111
|
+
elements.append({
|
|
112
|
+
"text": text,
|
|
113
|
+
"x": data['left'][i],
|
|
114
|
+
"y": data['top'][i],
|
|
115
|
+
"width": data['width'][i],
|
|
116
|
+
"height": data['height'][i],
|
|
117
|
+
"confidence": round(conf / 100, 2),
|
|
118
|
+
"line": line_idx
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
"elements": elements,
|
|
123
|
+
"fullText": '\n'.join(lines_text)
|
|
124
|
+
}
|
|
125
|
+
except ImportError:
|
|
126
|
+
return None # Fall back to CLI
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return {"error": f"pytesseract error: {str(e)}"}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main():
|
|
132
|
+
if len(sys.argv) < 2:
|
|
133
|
+
print(json.dumps({"error": "Usage: ocr-recognize.py <image-path>"}))
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
image_path = sys.argv[1]
|
|
137
|
+
if not os.path.isfile(image_path):
|
|
138
|
+
print(json.dumps({"error": f"Image not found: {image_path}"}))
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Try pytesseract first (better bounding boxes), fall back to CLI
|
|
142
|
+
result = ocr_with_pytesseract(image_path)
|
|
143
|
+
if result is None:
|
|
144
|
+
# pytesseract not installed, use CLI
|
|
145
|
+
if shutil.which('tesseract'):
|
|
146
|
+
result = ocr_with_tesseract_cli(image_path)
|
|
147
|
+
else:
|
|
148
|
+
result = {"error": "No OCR available. Install: sudo apt install tesseract-ocr"}
|
|
149
|
+
|
|
150
|
+
print(json.dumps(result))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == '__main__':
|
|
154
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env osascript -l JavaScript
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* _window-picker.jxa
|
|
5
|
+
*
|
|
6
|
+
* Shared reference implementation for "which window of a process is the
|
|
7
|
+
* user's real interaction window?". osascript cannot `require()` other
|
|
8
|
+
* scripts, so the helpers below are also INLINED into the consumer scripts.
|
|
9
|
+
* Treat this file as the source of truth — if you change scoring here, mirror
|
|
10
|
+
* the change in:
|
|
11
|
+
* - scripts/mac/focus-window.jxa
|
|
12
|
+
* - scripts/mac/get-foreground-window.jxa
|
|
13
|
+
*
|
|
14
|
+
* Intended consumers:
|
|
15
|
+
* - focus-window.jxa (uses findTitleMatchWindow + pickInteractionWindow fallback)
|
|
16
|
+
* - get-foreground-window.jxa (uses pickInteractionWindow)
|
|
17
|
+
*
|
|
18
|
+
* Heuristics, in order of weight:
|
|
19
|
+
* 1. Visible area (largest wins; rejects 0x0 outright)
|
|
20
|
+
* 2. Subrole penalty: AXFloatingWindow / AXDialog / AXSystemDialog are
|
|
21
|
+
* almost always popups; AXStandardWindow is preferred.
|
|
22
|
+
* 3. Title penalty: titles like "Downloads" / "Notifications" / "Updates"
|
|
23
|
+
* are commonly system-tray-ish popovers in apps like Xcode, Safari.
|
|
24
|
+
* 4. Z-order tiebreak: lower index ~= newer / more recently activated,
|
|
25
|
+
* so older-but-larger windows still beat tiny new popups via (1)+(2)+(3),
|
|
26
|
+
* but among equally-scored windows the older one wins (it's the window
|
|
27
|
+
* the user was already working in before the popup appeared).
|
|
28
|
+
*
|
|
29
|
+
* Bugs this addresses (clawdcursor #101):
|
|
30
|
+
* #2 focus_window ignored title= on multi-window apps and grabbed windows[0]
|
|
31
|
+
* #4 getActiveWindow grabbed windows[0] of the frontmost process, which on
|
|
32
|
+
* Xcode would surface the "Downloads" popup instead of the project window
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
function safeGet(obj, property, defaultValue) {
|
|
36
|
+
try {
|
|
37
|
+
var val = obj[property]();
|
|
38
|
+
return (val !== undefined && val !== null) ? val : defaultValue;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return defaultValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Reads {x, y, width, height} for a window; returns zeros if AXPosition/AXSize
|
|
45
|
+
// are not accessible (some background processes return EACCESS here).
|
|
46
|
+
function getWinBounds(win) {
|
|
47
|
+
var bounds = { x: 0, y: 0, width: 0, height: 0 };
|
|
48
|
+
try {
|
|
49
|
+
var p = win.position();
|
|
50
|
+
var s = win.size();
|
|
51
|
+
if (p && s) {
|
|
52
|
+
bounds.x = p[0] || 0;
|
|
53
|
+
bounds.y = p[1] || 0;
|
|
54
|
+
bounds.width = s[0] || 0;
|
|
55
|
+
bounds.height = s[1] || 0;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {}
|
|
58
|
+
return bounds;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Pulls the AXSubrole accessibility attribute, e.g. "AXStandardWindow",
|
|
62
|
+
// "AXFloatingWindow", "AXDialog", "AXSystemDialog". Returns '' if unknown.
|
|
63
|
+
function getWinSubrole(win) {
|
|
64
|
+
try {
|
|
65
|
+
return win.subrole() || '';
|
|
66
|
+
} catch (e) {}
|
|
67
|
+
try {
|
|
68
|
+
return win.attributes.byName('AXSubrole').value() || '';
|
|
69
|
+
} catch (e) {}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isLikelyTrayTitle(title) {
|
|
74
|
+
if (!title) return false;
|
|
75
|
+
var t = String(title).trim().toLowerCase();
|
|
76
|
+
return t === 'downloads' || t === 'notifications' || t === 'updates' ||
|
|
77
|
+
t === 'inspector' || t === 'activity';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Scores a window for "is this the user's real interaction window?".
|
|
81
|
+
// Higher score wins. Returns -Infinity for 0x0 / invisible windows.
|
|
82
|
+
function scoreWindow(win, index) {
|
|
83
|
+
var bounds = getWinBounds(win);
|
|
84
|
+
var area = bounds.width * bounds.height;
|
|
85
|
+
if (area <= 0) return -Infinity;
|
|
86
|
+
|
|
87
|
+
var score = area;
|
|
88
|
+
|
|
89
|
+
var subrole = getWinSubrole(win);
|
|
90
|
+
if (subrole === 'AXStandardWindow') {
|
|
91
|
+
score += 2000000; // strongly prefer standard windows
|
|
92
|
+
} else if (subrole === 'AXFloatingWindow') {
|
|
93
|
+
score -= 5000000; // popups, palettes
|
|
94
|
+
} else if (subrole === 'AXDialog' || subrole === 'AXSystemDialog') {
|
|
95
|
+
score -= 3000000; // modal dialogs
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var title = '';
|
|
99
|
+
try { title = win.name() || ''; } catch (e) {}
|
|
100
|
+
if (isLikelyTrayTitle(title)) score -= 1500000;
|
|
101
|
+
|
|
102
|
+
// Tiebreak: older windows (higher index) get a tiny nudge so popups
|
|
103
|
+
// (which arrive at index 0) lose ties to the underlying work window.
|
|
104
|
+
score += index * 10;
|
|
105
|
+
|
|
106
|
+
return score;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Picks the most likely user-interaction window from a window list.
|
|
110
|
+
// Returns { window, index } or null if no window has visible area.
|
|
111
|
+
function pickInteractionWindow(windows) {
|
|
112
|
+
var best = null;
|
|
113
|
+
var bestScore = -Infinity;
|
|
114
|
+
var bestIndex = -1;
|
|
115
|
+
for (var i = 0; i < windows.length; i++) {
|
|
116
|
+
try {
|
|
117
|
+
var w = windows[i];
|
|
118
|
+
var s = scoreWindow(w, i);
|
|
119
|
+
if (s > bestScore) {
|
|
120
|
+
bestScore = s;
|
|
121
|
+
best = w;
|
|
122
|
+
bestIndex = i;
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {}
|
|
125
|
+
}
|
|
126
|
+
if (!best) return null;
|
|
127
|
+
return { window: best, index: bestIndex };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Finds first window whose name contains `needle` (case-insensitive)
|
|
131
|
+
// AND has non-zero visible area. Used by focus-window.jxa.
|
|
132
|
+
function findTitleMatchWindow(windows, needle) {
|
|
133
|
+
if (!needle) return null;
|
|
134
|
+
var n = String(needle).toLowerCase();
|
|
135
|
+
for (var i = 0; i < windows.length; i++) {
|
|
136
|
+
try {
|
|
137
|
+
var w = windows[i];
|
|
138
|
+
var title = '';
|
|
139
|
+
try { title = w.name() || ''; } catch (e) {}
|
|
140
|
+
if (!title) continue;
|
|
141
|
+
if (title.toLowerCase().indexOf(n) === -1) continue;
|
|
142
|
+
var b = getWinBounds(w);
|
|
143
|
+
if (b.width * b.height <= 0) continue;
|
|
144
|
+
return { window: w, index: i, title: title };
|
|
145
|
+
} catch (e) {}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// This file is reference-only when invoked directly — print the helpers
|
|
151
|
+
// list so a curious developer running `osascript _window-picker.jxa` gets
|
|
152
|
+
// something useful back instead of nothing.
|
|
153
|
+
function run(argv) {
|
|
154
|
+
return JSON.stringify({
|
|
155
|
+
kind: 'reference',
|
|
156
|
+
helpers: [
|
|
157
|
+
'safeGet', 'getWinBounds', 'getWinSubrole',
|
|
158
|
+
'isLikelyTrayTitle', 'scoreWindow',
|
|
159
|
+
'pickInteractionWindow', 'findTitleMatchWindow'
|
|
160
|
+
],
|
|
161
|
+
note: 'osascript cannot require() — these helpers are inlined in focus-window.jxa and get-foreground-window.jxa. Keep all three in sync.'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# find-element.sh — Find UI elements on macOS using JXA + System Events
|
|
3
|
+
#
|
|
4
|
+
# Wrapper around find-element.jxa for UIDriver cross-platform support.
|
|
5
|
+
# Outputs JSON matching the Windows find-element.ps1 format:
|
|
6
|
+
# [{ name, automationId, controlType, className, processId, isEnabled, bounds }]
|
|
7
|
+
#
|
|
8
|
+
# Parameters:
|
|
9
|
+
# -Name <string> Match elements by name (contains, case-insensitive)
|
|
10
|
+
# -AutomationId <string> Match by description/identifier (mapped to name search)
|
|
11
|
+
# -ControlType <string> Match by role (e.g. Button, Edit, MenuItem)
|
|
12
|
+
# -ProcessId <number> Limit to a specific process
|
|
13
|
+
# -MaxResults <number> Max results (default 20)
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# chmod +x scripts/mac/find-element.sh
|
|
17
|
+
# ./scripts/mac/find-element.sh -Name "OK" -ControlType "Button" -ProcessId 1234
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
|
|
23
|
+
# Parse arguments
|
|
24
|
+
NAME=""
|
|
25
|
+
AUTOMATION_ID=""
|
|
26
|
+
CONTROL_TYPE=""
|
|
27
|
+
PROCESS_ID=""
|
|
28
|
+
MAX_RESULTS="20"
|
|
29
|
+
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
-Name) NAME="$2"; shift 2 ;;
|
|
33
|
+
-AutomationId) AUTOMATION_ID="$2"; shift 2 ;;
|
|
34
|
+
-ControlType) CONTROL_TYPE="$2"; shift 2 ;;
|
|
35
|
+
-ProcessId) PROCESS_ID="$2"; shift 2 ;;
|
|
36
|
+
-MaxResults) MAX_RESULTS="$2"; shift 2 ;;
|
|
37
|
+
*) shift ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
# Map Windows ControlType names to macOS accessibility roles
|
|
42
|
+
map_control_type() {
|
|
43
|
+
local ct="$1"
|
|
44
|
+
case "$ct" in
|
|
45
|
+
Button) echo "button" ;;
|
|
46
|
+
Edit) echo "text field" ;;
|
|
47
|
+
Text) echo "static text" ;;
|
|
48
|
+
CheckBox) echo "checkbox" ;;
|
|
49
|
+
RadioButton) echo "radio button" ;;
|
|
50
|
+
ComboBox) echo "pop up button" ;;
|
|
51
|
+
MenuItem) echo "menu item" ;;
|
|
52
|
+
Menu) echo "menu" ;;
|
|
53
|
+
MenuBar) echo "menu bar" ;;
|
|
54
|
+
List) echo "list" ;;
|
|
55
|
+
ListItem) echo "row" ;;
|
|
56
|
+
Tree) echo "outline" ;;
|
|
57
|
+
TreeItem) echo "row" ;;
|
|
58
|
+
Tab) echo "tab group" ;;
|
|
59
|
+
TabItem) echo "radio button" ;;
|
|
60
|
+
Slider) echo "slider" ;;
|
|
61
|
+
ProgressBar) echo "progress indicator" ;;
|
|
62
|
+
ToolBar) echo "toolbar" ;;
|
|
63
|
+
Window) echo "window" ;;
|
|
64
|
+
Group) echo "group" ;;
|
|
65
|
+
Image) echo "image" ;;
|
|
66
|
+
Hyperlink) echo "link" ;;
|
|
67
|
+
ScrollBar) echo "scroll bar" ;;
|
|
68
|
+
Table) echo "table" ;;
|
|
69
|
+
Document) echo "text area" ;;
|
|
70
|
+
*) echo "$ct" ;;
|
|
71
|
+
esac
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Build JXA arguments
|
|
75
|
+
JXA_ARGS=()
|
|
76
|
+
|
|
77
|
+
# Use Name or AutomationId as the search name
|
|
78
|
+
SEARCH_NAME="${NAME:-$AUTOMATION_ID}"
|
|
79
|
+
if [[ -n "$SEARCH_NAME" ]]; then
|
|
80
|
+
JXA_ARGS+=("-name" "$SEARCH_NAME")
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Map control type to macOS role
|
|
84
|
+
if [[ -n "$CONTROL_TYPE" ]]; then
|
|
85
|
+
MAPPED_ROLE=$(map_control_type "$CONTROL_TYPE")
|
|
86
|
+
JXA_ARGS+=("-role" "$MAPPED_ROLE")
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [[ -n "$PROCESS_ID" && "$PROCESS_ID" != "0" ]]; then
|
|
90
|
+
JXA_ARGS+=("-processId" "$PROCESS_ID")
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Run the JXA script
|
|
94
|
+
RAW_OUTPUT=$(osascript -l JavaScript "$SCRIPT_DIR/find-element.jxa" "${JXA_ARGS[@]}" 2>/dev/null || echo "[]")
|
|
95
|
+
|
|
96
|
+
# Transform the JXA output to match Windows format:
|
|
97
|
+
# JXA returns: { name, role, value, bounds, processId }
|
|
98
|
+
# Windows expects: { name, automationId, controlType, className, processId, isEnabled, bounds }
|
|
99
|
+
echo "$RAW_OUTPUT" | /usr/bin/python3 -c "
|
|
100
|
+
import sys, json
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
data = json.load(sys.stdin)
|
|
104
|
+
except:
|
|
105
|
+
print('[]')
|
|
106
|
+
sys.exit(0)
|
|
107
|
+
|
|
108
|
+
if isinstance(data, dict) and 'error' in data:
|
|
109
|
+
print(json.dumps(data))
|
|
110
|
+
sys.exit(0)
|
|
111
|
+
|
|
112
|
+
if not isinstance(data, list):
|
|
113
|
+
data = [data]
|
|
114
|
+
|
|
115
|
+
# Map macOS roles back to Windows ControlType names
|
|
116
|
+
ROLE_MAP = {
|
|
117
|
+
'AXButton': 'Button', 'button': 'Button',
|
|
118
|
+
'AXTextField': 'Edit', 'text field': 'Edit',
|
|
119
|
+
'AXStaticText': 'Text', 'static text': 'Text',
|
|
120
|
+
'AXCheckBox': 'CheckBox', 'checkbox': 'CheckBox',
|
|
121
|
+
'AXRadioButton': 'RadioButton', 'radio button': 'RadioButton',
|
|
122
|
+
'AXPopUpButton': 'ComboBox', 'pop up button': 'ComboBox',
|
|
123
|
+
'AXMenuItem': 'MenuItem', 'menu item': 'MenuItem',
|
|
124
|
+
'AXMenu': 'Menu', 'menu': 'Menu',
|
|
125
|
+
'AXMenuBar': 'MenuBar', 'menu bar': 'MenuBar',
|
|
126
|
+
'AXList': 'List', 'list': 'List',
|
|
127
|
+
'AXRow': 'ListItem', 'row': 'ListItem',
|
|
128
|
+
'AXOutline': 'Tree', 'outline': 'Tree',
|
|
129
|
+
'AXTabGroup': 'Tab', 'tab group': 'Tab',
|
|
130
|
+
'AXSlider': 'Slider', 'slider': 'Slider',
|
|
131
|
+
'AXProgressIndicator': 'ProgressBar', 'progress indicator': 'ProgressBar',
|
|
132
|
+
'AXToolbar': 'ToolBar', 'toolbar': 'ToolBar',
|
|
133
|
+
'AXWindow': 'Window', 'window': 'Window',
|
|
134
|
+
'AXGroup': 'Group', 'group': 'Group',
|
|
135
|
+
'AXImage': 'Image', 'image': 'Image',
|
|
136
|
+
'AXLink': 'Hyperlink', 'link': 'Hyperlink',
|
|
137
|
+
'AXScrollBar': 'ScrollBar', 'scroll bar': 'ScrollBar',
|
|
138
|
+
'AXTable': 'Table', 'table': 'Table',
|
|
139
|
+
'AXTextArea': 'Document', 'text area': 'Document',
|
|
140
|
+
'AXScrollArea': 'Pane', 'scroll area': 'Pane',
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
max_results = int('${MAX_RESULTS}')
|
|
144
|
+
results = []
|
|
145
|
+
|
|
146
|
+
for item in data[:max_results]:
|
|
147
|
+
role = item.get('role', '')
|
|
148
|
+
control_type = ROLE_MAP.get(role, role)
|
|
149
|
+
|
|
150
|
+
results.append({
|
|
151
|
+
'name': item.get('name', ''),
|
|
152
|
+
'automationId': '', # macOS doesn't have AutomationId; use identifier if available
|
|
153
|
+
'controlType': control_type,
|
|
154
|
+
'className': role, # Store original macOS role as className
|
|
155
|
+
'processId': item.get('processId', 0),
|
|
156
|
+
'isEnabled': True, # macOS doesn't easily expose enabled state in bulk
|
|
157
|
+
'bounds': item.get('bounds', {'x': 0, 'y': 0, 'width': 0, 'height': 0})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
print(json.dumps(results, separators=(',', ':')))
|
|
161
|
+
"
|