extra-desktop 0.3.2__tar.gz → 0.3.3__tar.gz
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.
- {extra_desktop-0.3.2/extra_desktop.egg-info → extra_desktop-0.3.3}/PKG-INFO +16 -8
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/README.md +5 -5
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/pyproject.toml +27 -12
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/__init__.py +1 -1
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/cli.py +100 -6
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/distiller.py +43 -13
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/shell.py +1 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/shell.py +3 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/input_engine.py +49 -30
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/shell.py +14 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/uia_plane.py +27 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/vision_parser.py +17 -6
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/__init__.py +22 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/fs.py +42 -7
- extra_desktop-0.3.3/src/extra/fastpath/relay.py +26 -0
- extra_desktop-0.3.3/src/extra/fastpath/tunnel.py +24 -0
- extra_desktop-0.3.3/src/extra/mcp/server.py +2859 -0
- extra_desktop-0.3.3/src/extra/relay/__init__.py +42 -0
- extra_desktop-0.3.3/src/extra/relay/client.py +246 -0
- extra_desktop-0.3.3/src/extra/relay/tunnel.py +257 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra_desktop.egg-info}/PKG-INFO +16 -8
- extra_desktop-0.3.3/src/extra_desktop.egg-info/SOURCES.txt +186 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src}/extra_desktop.egg-info/requires.txt +14 -2
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_composer_director_soul.py +4 -2
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_hierarchical_benchmarks.py +5 -4
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_issue4_bottlenecks.py +2 -6
- extra_desktop-0.3.3/tests/test_mcp_sse_transport.py +201 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pillar1_funnel.py +10 -3
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pillar2_installer.py +1 -1
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_security_hardening.py +94 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_tiered_verification.py +1 -1
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_tiered_verification_benchmarks.py +6 -4
- extra_desktop-0.3.2/extra_desktop.egg-info/SOURCES.txt +0 -250
- extra_desktop-0.3.2/mcp/server.py +0 -2521
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/LICENSE +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/NOTICE +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/setup.cfg +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/capture.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/composer/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/composer/blueprint.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/composer/folding.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/composer/lineage.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/composer/verifier.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/analyzer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/api_synthesizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/crystallizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/curator.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/evolution/harness_generator.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/focus.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/geometry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/input_engine.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/memory/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/memory/db.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/memory/embeddings.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/memory/ingest.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/memory/recall.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/auditor.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/config.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/dashboard.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/ledger.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/sanitizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/metrics/telemetry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/motion/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/motion/curves.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/motion/profiler.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/motion/stroke.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/motion/windmouse.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/base.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/atspi_plane.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/capture.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/focus.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/geometry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/input_engine.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/linux/permissions.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/ax_plane.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/capture.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/focus.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/geometry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/input_engine.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/macos/permissions.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/capture.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/child_session.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/focus.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/geometry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/platform/windows/pip_window.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/cdp_sniffer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/detector.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/scraper.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/session_vault.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/scout/synthesizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/compressor.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/critic.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/decider.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/eyes.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/gateman.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/grammar.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/runtime.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/schemas.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/soul/state_critic.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/stall_breaker.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/subtask.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/uia_plane.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/core/uninstall.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/bridge.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/browser.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/cli_harness.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/cli_hub.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/clipboard_html.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/shell.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/vectorizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/visualizer.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/web/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/fastpath/web_workspace.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src/extra}/mcp/__init__.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src}/extra_desktop.egg-info/dependency_links.txt +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src}/extra_desktop.egg-info/entry_points.txt +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3/src}/extra_desktop.egg-info/top_level.txt +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_acceleration_suite.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_app_registry_dynamic.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_bridge_sandbox.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_canvas_stall_breaker.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_child_session.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_cli_harness.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_core_engine.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_dynamic_batching.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_evolution.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_evolution_harness.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_geometry_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_human_mouse.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_input_engine_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_launch_maximize.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_lineage.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_atspi.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_capture.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_focus.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_geometry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_input.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_linux_shell.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_ax.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_core.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_focus_shell.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_indicators.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_input.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_perception.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_macos_safety.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_mcp_schemas_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_memory_kuzu.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_memory_subsystem_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_metrics.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_milestone_budgeting.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_paint_remediation.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_perception_vision_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pillar3_tcc_playbook.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pillar4_web_edge.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pillar5_package_ecosystem.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_pre_release_fixes.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_process_shell_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_scout.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_scout_evolution_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_soul_benchmarks.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_soul_evolution.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_soul_runtime.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_stall_breaker_cognitive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_stall_breaker_exhaustive.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_state_critic.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_subtask_runner.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_telemetry.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_ui_distiller.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_uninstall.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_universal_tasksuite.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_verification_mcp.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_vision_parser.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_visual_grounding.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_web_to_api.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_web_workspace.py +0 -0
- {extra_desktop-0.3.2 → extra_desktop-0.3.3}/tests/test_windows_indicators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: extra-desktop
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Flashless Linux, Windows & macOS Computer-Use Engine & Model Context Protocol (MCP) Server
|
|
5
5
|
Author-email: AIYantra Engineering Team <engineering@yantraos.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,14 +11,15 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
License-File: NOTICE
|
|
14
|
-
Requires-Dist: mcp
|
|
14
|
+
Requires-Dist: mcp<2,>=1.0.0
|
|
15
|
+
Requires-Dist: websockets>=13.0
|
|
15
16
|
Requires-Dist: playwright>=1.47.0
|
|
16
17
|
Requires-Dist: pillow>=10.4.0
|
|
17
18
|
Requires-Dist: beautifulsoup4>=4.12.3
|
|
18
19
|
Requires-Dist: imagehash>=4.3.1
|
|
19
20
|
Requires-Dist: numpy>=1.26.0
|
|
20
21
|
Requires-Dist: psutil>=6.0.0
|
|
21
|
-
Requires-Dist: kuzu>=0.11.0
|
|
22
|
+
Requires-Dist: kuzu>=0.11.0; python_version < "3.14"
|
|
22
23
|
Requires-Dist: fastembed>=0.8.0
|
|
23
24
|
Requires-Dist: onnxruntime>=1.19.0
|
|
24
25
|
Requires-Dist: pywin32>=306; sys_platform == "win32"
|
|
@@ -32,12 +33,19 @@ Requires-Dist: pyobjc-framework-ScreenCaptureKit>=10.0; sys_platform == "darwin"
|
|
|
32
33
|
Requires-Dist: evdev>=1.7.0; sys_platform == "linux"
|
|
33
34
|
Requires-Dist: mss>=9.0.1; sys_platform == "linux"
|
|
34
35
|
Requires-Dist: jeepney>=0.8.0; sys_platform == "linux"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.11.0; extra == "dev"
|
|
41
|
+
Provides-Extra: benchmark
|
|
42
|
+
Requires-Dist: pytest-benchmark>=4.0.0; extra == "benchmark"
|
|
35
43
|
Dynamic: license-file
|
|
36
44
|
|
|
37
45
|
<div align="center">
|
|
38
46
|
|
|
39
47
|
<a href="https://extra.yantraos.com">
|
|
40
|
-
<img src="
|
|
48
|
+
<img src="assets/logo.png" alt="Extra — Get extra from your AI" width="420" />
|
|
41
49
|
</a>
|
|
42
50
|
|
|
43
51
|
# Extra
|
|
@@ -54,7 +62,7 @@ Dynamic: license-file
|
|
|
54
62
|
[](https://yantraos.com)
|
|
55
63
|
[](https://github.com/AIYantra/extra)
|
|
56
64
|
|
|
57
|
-
[Website](https://extra.yantraos.com) • [Quickstart](#-quickstart-1-minute) • [Linux Guide](README_LINUX.md) • [macOS Guide](README_MACOS.md) • [Architecture](ARCHITECTURE.md) • [License](LICENSE)
|
|
65
|
+
[Website](https://extra.yantraos.com) • [Quickstart](#-quickstart-1-minute) • [Linux Guide](README_LINUX.md) • [macOS Guide](README_MACOS.md) • [Architecture](docs/ARCHITECTURE.md) • [License](LICENSE)
|
|
58
66
|
|
|
59
67
|
<br/>
|
|
60
68
|
<br/>
|
|
@@ -278,14 +286,14 @@ extra/
|
|
|
278
286
|
├── cli.py # CLI runner (extra doctor, test, indicators, run, inspect, snap)
|
|
279
287
|
├── install.ps1 # 1-line PowerShell installer (Windows)
|
|
280
288
|
├── install.sh # 1-line bash installer (macOS)
|
|
281
|
-
├── pyproject.toml # PyPI package manifest (extra-desktop v0.3.
|
|
282
|
-
├── package.json # npm package manifest (@yantraos/extra-desktop v0.3.
|
|
289
|
+
├── pyproject.toml # PyPI package manifest (extra-desktop v0.3.3)
|
|
290
|
+
├── package.json # npm package manifest (@yantraos/extra-desktop v0.3.3)
|
|
283
291
|
├── server.json # Official MCP registry manifest
|
|
284
292
|
├── glama.json # Glama MCP ecosystem manifest
|
|
285
293
|
├── smithery.yaml # Smithery 1-click deployment manifest
|
|
286
294
|
├── requirements.txt # Enterprise-audited dependency manifest
|
|
287
295
|
├── STARTER_PROMPT.md # Master AI system prompt & setup directive
|
|
288
|
-
├──
|
|
296
|
+
├── docs/ # Full System Architecture & Specifications
|
|
289
297
|
└── RELEASE_NOTES.md # Comprehensive version release notes
|
|
290
298
|
```
|
|
291
299
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
3
|
<a href="https://extra.yantraos.com">
|
|
4
|
-
<img src="
|
|
4
|
+
<img src="assets/logo.png" alt="Extra — Get extra from your AI" width="420" />
|
|
5
5
|
</a>
|
|
6
6
|
|
|
7
7
|
# Extra
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
[](https://yantraos.com)
|
|
19
19
|
[](https://github.com/AIYantra/extra)
|
|
20
20
|
|
|
21
|
-
[Website](https://extra.yantraos.com) • [Quickstart](#-quickstart-1-minute) • [Linux Guide](README_LINUX.md) • [macOS Guide](README_MACOS.md) • [Architecture](ARCHITECTURE.md) • [License](LICENSE)
|
|
21
|
+
[Website](https://extra.yantraos.com) • [Quickstart](#-quickstart-1-minute) • [Linux Guide](README_LINUX.md) • [macOS Guide](README_MACOS.md) • [Architecture](docs/ARCHITECTURE.md) • [License](LICENSE)
|
|
22
22
|
|
|
23
23
|
<br/>
|
|
24
24
|
<br/>
|
|
@@ -242,14 +242,14 @@ extra/
|
|
|
242
242
|
├── cli.py # CLI runner (extra doctor, test, indicators, run, inspect, snap)
|
|
243
243
|
├── install.ps1 # 1-line PowerShell installer (Windows)
|
|
244
244
|
├── install.sh # 1-line bash installer (macOS)
|
|
245
|
-
├── pyproject.toml # PyPI package manifest (extra-desktop v0.3.
|
|
246
|
-
├── package.json # npm package manifest (@yantraos/extra-desktop v0.3.
|
|
245
|
+
├── pyproject.toml # PyPI package manifest (extra-desktop v0.3.3)
|
|
246
|
+
├── package.json # npm package manifest (@yantraos/extra-desktop v0.3.3)
|
|
247
247
|
├── server.json # Official MCP registry manifest
|
|
248
248
|
├── glama.json # Glama MCP ecosystem manifest
|
|
249
249
|
├── smithery.yaml # Smithery 1-click deployment manifest
|
|
250
250
|
├── requirements.txt # Enterprise-audited dependency manifest
|
|
251
251
|
├── STARTER_PROMPT.md # Master AI system prompt & setup directive
|
|
252
|
-
├──
|
|
252
|
+
├── docs/ # Full System Architecture & Specifications
|
|
253
253
|
└── RELEASE_NOTES.md # Comprehensive version release notes
|
|
254
254
|
```
|
|
255
255
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "extra-desktop"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "Flashless Linux, Windows & macOS Computer-Use Engine & Model Context Protocol (MCP) Server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,14 +13,15 @@ authors = [
|
|
|
13
13
|
{ name = "AIYantra Engineering Team", email = "engineering@yantraos.com" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"mcp>=1.0.0",
|
|
16
|
+
"mcp>=1.0.0,<2",
|
|
17
|
+
"websockets>=13.0",
|
|
17
18
|
"playwright>=1.47.0",
|
|
18
19
|
"pillow>=10.4.0",
|
|
19
20
|
"beautifulsoup4>=4.12.3",
|
|
20
21
|
"imagehash>=4.3.1",
|
|
21
22
|
"numpy>=1.26.0",
|
|
22
23
|
"psutil>=6.0.0",
|
|
23
|
-
"kuzu>=0.11.0",
|
|
24
|
+
"kuzu>=0.11.0; python_version < '3.14'",
|
|
24
25
|
"fastembed>=0.8.0",
|
|
25
26
|
"onnxruntime>=1.19.0",
|
|
26
27
|
"pywin32>=306; sys_platform == 'win32'",
|
|
@@ -36,6 +37,17 @@ dependencies = [
|
|
|
36
37
|
"jeepney>=0.8.0; sys_platform == 'linux'",
|
|
37
38
|
]
|
|
38
39
|
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8.0.0",
|
|
43
|
+
"pytest-asyncio>=0.24.0",
|
|
44
|
+
"ruff>=0.6.0",
|
|
45
|
+
"mypy>=1.11.0",
|
|
46
|
+
]
|
|
47
|
+
benchmark = [
|
|
48
|
+
"pytest-benchmark>=4.0.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
39
51
|
[project.scripts]
|
|
40
52
|
extra = "extra.cli:main"
|
|
41
53
|
extra-desktop = "extra.cli:main"
|
|
@@ -45,17 +57,20 @@ Homepage = "https://extra.yantraos.com"
|
|
|
45
57
|
Repository = "https://github.com/AIYantra/extra"
|
|
46
58
|
Documentation = "https://extra.yantraos.com/docs"
|
|
47
59
|
|
|
48
|
-
[tool.setuptools.
|
|
49
|
-
|
|
50
|
-
"extra.core" = "core"
|
|
51
|
-
"extra.core.platform" = "core/platform"
|
|
52
|
-
"extra.core.platform.windows" = "core/platform/windows"
|
|
53
|
-
"extra.core.platform.macos" = "core/platform/macos"
|
|
54
|
-
"extra.core.platform.linux" = "core/platform/linux"
|
|
55
|
-
"extra.fastpath" = "fastpath"
|
|
56
|
-
"extra.mcp" = "mcp"
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
57
62
|
|
|
58
63
|
[tool.pytest.ini_options]
|
|
59
64
|
testpaths = ["tests"]
|
|
65
|
+
pythonpath = ["src"]
|
|
60
66
|
addopts = "-p no:faulthandler"
|
|
67
|
+
markers = [
|
|
68
|
+
"unit: Fast isolated unit tests with zero system dependencies",
|
|
69
|
+
"integration: Subsystem integration tests",
|
|
70
|
+
"windows: Tests requiring Windows Win32 / UIA subsystems",
|
|
71
|
+
"linux: Tests requiring Linux X11 / Wayland / AT-SPI2 subsystems",
|
|
72
|
+
"macos: Tests requiring macOS Quartz / Accessibility subsystems",
|
|
73
|
+
"benchmark: Performance profiling and stress benchmark suites",
|
|
74
|
+
"security: Security audit and vulnerability verification tests",
|
|
75
|
+
]
|
|
61
76
|
|
|
@@ -22,7 +22,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
22
22
|
try:
|
|
23
23
|
from extra import __version__
|
|
24
24
|
except Exception:
|
|
25
|
-
__version__ = "0.3.
|
|
25
|
+
__version__ = "0.3.3"
|
|
26
26
|
|
|
27
27
|
from extra.core.capture import capture_screen
|
|
28
28
|
from extra.core.focus import (
|
|
@@ -359,8 +359,45 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
359
359
|
os.environ["EXTRA_ISOLATED_DESKTOP"] = "1"
|
|
360
360
|
if getattr(args, "pip", False):
|
|
361
361
|
os.environ["EXTRA_PIP_PREVIEW"] = "1"
|
|
362
|
+
|
|
363
|
+
transport = getattr(args, "transport", "stdio")
|
|
364
|
+
host = getattr(args, "host", "127.0.0.1")
|
|
365
|
+
port = getattr(args, "port", 8000)
|
|
366
|
+
auth_token = getattr(args, "auth_token", None)
|
|
367
|
+
allowed_hosts = getattr(args, "allowed_hosts", None)
|
|
368
|
+
tunnel = getattr(args, "tunnel", False)
|
|
369
|
+
if allowed_hosts and isinstance(allowed_hosts, str):
|
|
370
|
+
allowed_hosts = [h.strip() for h in allowed_hosts.split(",") if h.strip()]
|
|
371
|
+
|
|
362
372
|
from extra.mcp.server import main
|
|
363
|
-
main()
|
|
373
|
+
main(transport=transport, host=host, port=port, auth_token=auth_token, allowed_hosts=allowed_hosts, tunnel=tunnel)
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def cmd_serve(args: argparse.Namespace) -> int:
|
|
378
|
+
"""Starts the Extra MCP server over SSE HTTP transport (for ChatGPT plugins & web agents)."""
|
|
379
|
+
setattr(args, "transport", "sse")
|
|
380
|
+
return cmd_run(args)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def cmd_connect(args: argparse.Namespace) -> int:
|
|
384
|
+
"""Connects this desktop to the Extra Cloud Hub for ChatGPT automation."""
|
|
385
|
+
from extra.relay import start_relay, DEFAULT_RELAY_URL
|
|
386
|
+
hub_url = getattr(args, "hub", None) or os.environ.get("EXTRA_HUB_URL") or DEFAULT_RELAY_URL
|
|
387
|
+
token = getattr(args, "token", None)
|
|
388
|
+
if not token or token == "anonymous":
|
|
389
|
+
token = os.environ.get("EXTRA_KEY") or os.environ.get("EXTRA_RELAY_KEY")
|
|
390
|
+
if not token or token == "anonymous":
|
|
391
|
+
print("[!] Error: Workstation link key is required to connect to the Extra Cloud Hub.")
|
|
392
|
+
print(" Get your key from your dashboard at: https://extra.yantraos.com/dashboard")
|
|
393
|
+
print(" Then run:")
|
|
394
|
+
print(" extra connect --key <your_key>")
|
|
395
|
+
return 1
|
|
396
|
+
start_relay(
|
|
397
|
+
hub_url=hub_url,
|
|
398
|
+
token=token,
|
|
399
|
+
device_name=getattr(args, "name", None),
|
|
400
|
+
)
|
|
364
401
|
return 0
|
|
365
402
|
|
|
366
403
|
|
|
@@ -836,7 +873,7 @@ def cmd_update(args: argparse.Namespace) -> int:
|
|
|
836
873
|
if pyproject_file.exists():
|
|
837
874
|
try:
|
|
838
875
|
subprocess.run(
|
|
839
|
-
[sys.executable, "-m", "pip", "install", "--quiet", "-e", str(repo_dir)],
|
|
876
|
+
[sys.executable, "-m", "pip", "install", "--quiet", "--no-deps", "-e", str(repo_dir)],
|
|
840
877
|
check=True,
|
|
841
878
|
)
|
|
842
879
|
print(" [OK] Package editable link verified.")
|
|
@@ -845,6 +882,32 @@ def cmd_update(args: argparse.Namespace) -> int:
|
|
|
845
882
|
else:
|
|
846
883
|
print("\n[Dependency Refresh] Skipped (--skip-deps specified).")
|
|
847
884
|
|
|
885
|
+
# Phase 2b: Refresh CLI executable wrappers with updated PYTHONPATH
|
|
886
|
+
try:
|
|
887
|
+
bin_dir = Path.home() / ".extra" / "bin"
|
|
888
|
+
if bin_dir.exists():
|
|
889
|
+
if platform.system() == "Windows":
|
|
890
|
+
cmd_wrapper = bin_dir / "extra.cmd"
|
|
891
|
+
cmd_content = (
|
|
892
|
+
f"@echo off\n"
|
|
893
|
+
f"set PYTHONPATH={repo_dir}\\src;{repo_dir};%PYTHONPATH%\n"
|
|
894
|
+
f'"{sys.executable}" -m extra.cli %*\n'
|
|
895
|
+
)
|
|
896
|
+
cmd_wrapper.write_text(cmd_content, encoding="ascii")
|
|
897
|
+
print(" [OK] Updated Windows CLI wrapper in ~/.extra/bin/extra.cmd.")
|
|
898
|
+
else:
|
|
899
|
+
sh_wrapper = bin_dir / "extra"
|
|
900
|
+
sh_content = (
|
|
901
|
+
f"#!/usr/bin/env bash\n"
|
|
902
|
+
f'export PYTHONPATH="{repo_dir}/src:{repo_dir}:$PYTHONPATH"\n'
|
|
903
|
+
f'exec "{sys.executable}" -m extra.cli "$@"\n'
|
|
904
|
+
)
|
|
905
|
+
sh_wrapper.write_text(sh_content, encoding="utf-8")
|
|
906
|
+
sh_wrapper.chmod(0o755)
|
|
907
|
+
print(" [OK] Updated Unix CLI wrapper in ~/.extra/bin/extra.")
|
|
908
|
+
except Exception as ex:
|
|
909
|
+
print(f" [WARN] Could not refresh CLI wrapper: {ex}")
|
|
910
|
+
|
|
848
911
|
# Phase 3: AI Rules & Skills Synchronization
|
|
849
912
|
sync_ai_rules_and_skills(repo_dir)
|
|
850
913
|
|
|
@@ -1035,7 +1098,8 @@ def check_first_run_consent() -> None:
|
|
|
1035
1098
|
save_config(cfg)
|
|
1036
1099
|
|
|
1037
1100
|
|
|
1038
|
-
def
|
|
1101
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
1102
|
+
"""Builds and returns the top-level ArgumentParser for Extra CLI."""
|
|
1039
1103
|
parser = argparse.ArgumentParser(
|
|
1040
1104
|
prog="extra",
|
|
1041
1105
|
description="Extra — Flashless macOS, Windows & Linux Computer-Use Engine & MCP Server",
|
|
@@ -1069,11 +1133,32 @@ def main() -> None:
|
|
|
1069
1133
|
update_parser.add_argument("--force", action="store_true", help="Force update and reset local changes to upstream")
|
|
1070
1134
|
|
|
1071
1135
|
# run
|
|
1072
|
-
run_parser = subparsers.add_parser("run", help="Start the MCP Server on stdio")
|
|
1073
|
-
run_parser.add_argument("--transport", default="stdio", choices=["stdio"], help="Transport mode")
|
|
1136
|
+
run_parser = subparsers.add_parser("run", help="Start the MCP Server on stdio or SSE")
|
|
1137
|
+
run_parser.add_argument("--transport", default="stdio", choices=["stdio", "sse", "streamable-http"], help="Transport mode (default: stdio)")
|
|
1138
|
+
run_parser.add_argument("--host", default="127.0.0.1", help="Host IP to bind to when running SSE or HTTP transport (default: 127.0.0.1)")
|
|
1139
|
+
run_parser.add_argument("--port", "-p", type=int, default=8000, help="Port to bind to when running SSE or HTTP transport (default: 8000)")
|
|
1140
|
+
run_parser.add_argument("--auth-token", "--auth", default=None, help="Bearer authorization token to secure SSE transport")
|
|
1141
|
+
run_parser.add_argument("--allowed-hosts", default=None, help="Comma-separated allowed host header values for DNS rebinding protection")
|
|
1142
|
+
run_parser.add_argument("--tunnel", "-t", action="store_true", help="Launch secure Cloudflare Quick Tunnel (public HTTPS) for pairing with ChatGPT or remote agents")
|
|
1074
1143
|
run_parser.add_argument("--isolate", action="store_true", help="Confine automation to isolated child session / virtual desktop")
|
|
1075
1144
|
run_parser.add_argument("--pip", action="store_true", help="Launch live Picture-in-Picture floating preview window")
|
|
1076
1145
|
|
|
1146
|
+
# serve (ChatGPT Plugin & Remote Web Agent bridge)
|
|
1147
|
+
serve_parser = subparsers.add_parser("serve", help="Start the Extra MCP Server over SSE HTTP transport (for ChatGPT plugins & web agents)")
|
|
1148
|
+
serve_parser.add_argument("--host", default="127.0.0.1", help="Host IP to bind to (default: 127.0.0.1; use 0.0.0.0 for public/tunnel)")
|
|
1149
|
+
serve_parser.add_argument("--port", "-p", type=int, default=8000, help="Port to bind to (default: 8000)")
|
|
1150
|
+
serve_parser.add_argument("--auth-token", "--auth", default=None, help="Bearer authorization token to secure SSE transport")
|
|
1151
|
+
serve_parser.add_argument("--allowed-hosts", default=None, help="Comma-separated allowed host headers (e.g. *.trycloudflare.com)")
|
|
1152
|
+
serve_parser.add_argument("--tunnel", "-t", action="store_true", help="Launch secure Cloudflare Quick Tunnel (public HTTPS) for pairing with ChatGPT or remote agents")
|
|
1153
|
+
serve_parser.add_argument("--isolate", action="store_true", help="Confine automation to isolated child session / virtual desktop")
|
|
1154
|
+
serve_parser.add_argument("--pip", action="store_true", help="Launch live Picture-in-Picture floating preview window")
|
|
1155
|
+
|
|
1156
|
+
# connect (Desktop Relay Client for ChatGPT & Cloud Hub)
|
|
1157
|
+
connect_parser = subparsers.add_parser("connect", help="Connect this desktop to the Extra Cloud Hub for ChatGPT automation")
|
|
1158
|
+
connect_parser.add_argument("--hub", default=None, help="WebSocket Relay Hub URL (default: official extra.yantraos.com relay)")
|
|
1159
|
+
connect_parser.add_argument("--key", "--token", dest="token", default="anonymous", help="Setup key or authentication token from extra.yantraos.com account")
|
|
1160
|
+
connect_parser.add_argument("--name", default=None, help="Custom friendly device name for this workstation")
|
|
1161
|
+
|
|
1077
1162
|
# pip (Picture-in-Picture floating preview window)
|
|
1078
1163
|
pip_parser = subparsers.add_parser("pip", help="Control hardware-accelerated Picture-in-Picture floating preview window")
|
|
1079
1164
|
pip_parser.add_argument("action", nargs="?", default="demo", choices=["demo", "start", "stop"], help="Action: demo (live 3s preview), start, stop")
|
|
@@ -1154,6 +1239,11 @@ def main() -> None:
|
|
|
1154
1239
|
audit_parser.add_argument("--record", "-r", action="store_true", help="Record audited mission metrics into local impact_ledger.json")
|
|
1155
1240
|
audit_parser.add_argument("--json", action="store_true", help="Output audit results in raw JSON")
|
|
1156
1241
|
|
|
1242
|
+
return parser
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
def main() -> None:
|
|
1246
|
+
parser = build_parser()
|
|
1157
1247
|
args = parser.parse_args()
|
|
1158
1248
|
|
|
1159
1249
|
if not args.command:
|
|
@@ -1195,6 +1285,10 @@ def main() -> None:
|
|
|
1195
1285
|
sys.exit(cmd_uninstall(args))
|
|
1196
1286
|
elif args.command == "run":
|
|
1197
1287
|
sys.exit(cmd_run(args))
|
|
1288
|
+
elif args.command == "serve":
|
|
1289
|
+
sys.exit(cmd_serve(args))
|
|
1290
|
+
elif args.command == "connect":
|
|
1291
|
+
sys.exit(cmd_connect(args))
|
|
1198
1292
|
elif args.command == "pip":
|
|
1199
1293
|
sys.exit(cmd_pip(args))
|
|
1200
1294
|
elif args.command == "inspect":
|
|
@@ -242,19 +242,7 @@ class UIDistiller:
|
|
|
242
242
|
except Exception as ex:
|
|
243
243
|
logger.debug("Edge Vision fallback skipped: %s", ex)
|
|
244
244
|
|
|
245
|
-
#
|
|
246
|
-
final_elements = filtered_elements[:max_elements]
|
|
247
|
-
|
|
248
|
-
# 4. Spatially Sort (Top-to-Bottom, Left-to-Right by rows)
|
|
249
|
-
sorted_elements = self._spatial_sort(final_elements)
|
|
250
|
-
|
|
251
|
-
# Count interactives
|
|
252
|
-
interactive_count = sum(
|
|
253
|
-
1 for el in sorted_elements
|
|
254
|
-
if el.control_type in ACTION_CONTROL_TYPES or el.control_type in INPUT_CONTROL_TYPES
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
# 4.5 Discover High-Contrast Work Surface / Canvas Viewport (< 5ms)
|
|
245
|
+
# 4. Discover High-Contrast Work Surface / Custom Canvas Viewport (< 5ms)
|
|
258
246
|
work_surface_dict = None
|
|
259
247
|
if target_hwnd:
|
|
260
248
|
try:
|
|
@@ -264,9 +252,51 @@ class UIDistiller:
|
|
|
264
252
|
ws = self._vision_parser.detect_work_surface_in_window(target_hwnd)
|
|
265
253
|
if ws:
|
|
266
254
|
work_surface_dict = ws.to_dict()
|
|
255
|
+
|
|
256
|
+
# Universal Hybrid Canvas Vision:
|
|
257
|
+
# Detect opaque, custom-rendered surfaces (e.g. Qt/WebGL/DirectX canvas viewports,
|
|
258
|
+
# chat lists, artboards) where accessibility provides zero/minimal child elements.
|
|
259
|
+
ws_left, ws_top, ws_right, ws_bottom = ws.left, ws.top, ws.right, ws.bottom
|
|
260
|
+
elements_inside_ws = [
|
|
261
|
+
el for el in filtered_elements
|
|
262
|
+
if ws_left <= el.center[0] <= ws_right and ws_top <= el.center[1] <= ws_bottom
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
# If the work surface spans a major area (>= 160x160 px) but has <= 2 accessibility controls inside it:
|
|
266
|
+
if ws.width >= 160 and ws.height >= 160 and len(elements_inside_ws) <= 2:
|
|
267
|
+
try:
|
|
268
|
+
canvas_crop = (ws_left, ws_top, ws_right, ws_bottom)
|
|
269
|
+
vision_canvas_elements = self._vision_parser.parse_window(
|
|
270
|
+
target_hwnd, max_elements=max_elements, crop_box=canvas_crop
|
|
271
|
+
)
|
|
272
|
+
if vision_canvas_elements:
|
|
273
|
+
existing_names = {
|
|
274
|
+
(el.name or "").lower().strip()
|
|
275
|
+
for el in filtered_elements if el.name
|
|
276
|
+
}
|
|
277
|
+
for vel in vision_canvas_elements:
|
|
278
|
+
vel_name = (vel.name or "").strip().lower()
|
|
279
|
+
if not vel_name or vel_name not in existing_names:
|
|
280
|
+
filtered_elements.append(vel)
|
|
281
|
+
if vel_name:
|
|
282
|
+
existing_names.add(vel_name)
|
|
283
|
+
except Exception as c_ex:
|
|
284
|
+
logger.debug("Canvas vision parsing skipped: %s", c_ex)
|
|
267
285
|
except Exception as ex:
|
|
268
286
|
logger.debug("Work surface viewport detection skipped: %s", ex)
|
|
269
287
|
|
|
288
|
+
# Truncate to max_elements limit
|
|
289
|
+
final_elements = filtered_elements[:max_elements]
|
|
290
|
+
|
|
291
|
+
# 4.5 Spatially Sort (Top-to-Bottom, Left-to-Right by rows)
|
|
292
|
+
sorted_elements = self._spatial_sort(final_elements)
|
|
293
|
+
|
|
294
|
+
# Count interactives
|
|
295
|
+
interactive_count = sum(
|
|
296
|
+
1 for el in sorted_elements
|
|
297
|
+
if el.control_type in ACTION_CONTROL_TYPES or el.control_type in INPUT_CONTROL_TYPES
|
|
298
|
+
)
|
|
299
|
+
|
|
270
300
|
# 5. Format Content
|
|
271
301
|
fmt_clean = format.lower().strip()
|
|
272
302
|
if fmt_clean == "compact":
|
|
@@ -400,6 +400,7 @@ BUILTIN_APP_FALLBACKS: Dict[str, Dict[str, Any]] = {
|
|
|
400
400
|
},
|
|
401
401
|
"blender": {"candidates": ["blender"], "type": "bin", "proc": "blender"},
|
|
402
402
|
"vlc": {"candidates": ["vlc"], "type": "bin", "proc": "vlc"},
|
|
403
|
+
"canva": {"candidates": ["canva"], "type": "bin", "proc": "canva"},
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
# Compatibility dictionary mapping directly to target names
|
|
@@ -41,6 +41,9 @@ BUILTIN_MAC_APP_REGISTRY: Dict[str, Dict[str, str]] = {
|
|
|
41
41
|
"photos": {"target": "Photos", "bundle": "/System/Applications/Photos.app", "proc": "Photos", "type": "app"},
|
|
42
42
|
"code": {"target": "Visual Studio Code", "bundle": "/Applications/Visual Studio Code.app", "proc": "Code", "type": "app"},
|
|
43
43
|
"vscode": {"target": "Visual Studio Code", "bundle": "/Applications/Visual Studio Code.app", "proc": "Code", "type": "app"},
|
|
44
|
+
"vlc": {"target": "VLC", "bundle": "/Applications/VLC.app", "proc": "VLC", "type": "app"},
|
|
45
|
+
"blender": {"target": "Blender", "bundle": "/Applications/Blender.app", "proc": "Blender", "type": "app"},
|
|
46
|
+
"canva": {"target": "Canva", "bundle": "/Applications/Canva.app", "proc": "Canva", "type": "app"},
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
MAC_APP_REGISTRY: Dict[str, Dict[str, str]] = dict(BUILTIN_MAC_APP_REGISTRY)
|
|
@@ -211,52 +211,71 @@ def _send_inputs(inputs: List[INPUT]) -> int:
|
|
|
211
211
|
return int(user32.SendInput(n, arr, ctypes.sizeof(INPUT)))
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
def _release_stuck_modifiers() -> None:
|
|
215
|
+
"""Releases any lingering modifier keys (Ctrl, Alt, Shift, Win) to avoid keystroke corruption."""
|
|
216
|
+
mods = (
|
|
217
|
+
win32con.VK_CONTROL, win32con.VK_LCONTROL, win32con.VK_RCONTROL,
|
|
218
|
+
win32con.VK_MENU, win32con.VK_LMENU, win32con.VK_RMENU,
|
|
219
|
+
win32con.VK_SHIFT, win32con.VK_LSHIFT, win32con.VK_RSHIFT,
|
|
220
|
+
win32con.VK_LWIN, win32con.VK_RWIN,
|
|
221
|
+
)
|
|
222
|
+
for vk in mods:
|
|
223
|
+
if user32.GetAsyncKeyState(vk) & 0x8000:
|
|
224
|
+
ku = INPUT(type=INPUT_KEYBOARD)
|
|
225
|
+
ku.ki = KEYBDINPUT(wVk=vk, wScan=0, dwFlags=KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)
|
|
226
|
+
_send_inputs([ku])
|
|
227
|
+
|
|
228
|
+
|
|
214
229
|
def instant_type(text: str, press_enter: bool = False) -> None:
|
|
215
230
|
"""
|
|
216
|
-
Injects Unicode strings directly into the Windows input queue
|
|
231
|
+
Injects Unicode strings directly into the Windows input queue with atomic hardware delivery.
|
|
217
232
|
Uses Win32 KEYEVENTF_UNICODE with UTF-16 code units (supporting surrogate pairs for emojis).
|
|
233
|
+
Batches KeyDown and KeyUp pairs atomically to prevent WinUI 3 input buffer desynchronization.
|
|
234
|
+
Translates newlines to physical Enter keystrokes to ensure 100% fidelity across native editors.
|
|
218
235
|
"""
|
|
219
236
|
if not text and not press_enter:
|
|
220
237
|
return
|
|
221
238
|
|
|
222
|
-
|
|
239
|
+
_release_stuck_modifiers()
|
|
223
240
|
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
code_units = [
|
|
227
|
-
int.from_bytes(utf16_bytes[i : i + 2], "little")
|
|
228
|
-
for i in range(0, len(utf16_bytes), 2)
|
|
229
|
-
]
|
|
241
|
+
# Normalize line endings to avoid duplicate carriage returns
|
|
242
|
+
clean_text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
230
243
|
|
|
231
|
-
for
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
wVk=0,
|
|
236
|
-
|
|
237
|
-
dwFlags=
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
for ch in clean_text:
|
|
245
|
+
if ch == "\n":
|
|
246
|
+
# Translate newline to physical Enter keystroke for native Win32 & WinUI edit controls
|
|
247
|
+
kd_enter = INPUT(type=INPUT_KEYBOARD)
|
|
248
|
+
kd_enter.ki = KEYBDINPUT(wVk=win32con.VK_RETURN, wScan=0, dwFlags=0, time=0, dwExtraInfo=0)
|
|
249
|
+
ku_enter = INPUT(type=INPUT_KEYBOARD)
|
|
250
|
+
ku_enter.ki = KEYBDINPUT(wVk=win32con.VK_RETURN, wScan=0, dwFlags=KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)
|
|
251
|
+
_send_inputs([kd_enter, ku_enter])
|
|
252
|
+
time.sleep(0.003)
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
# Encode character to UTF-16-LE words to capture surrogate pairs
|
|
256
|
+
utf16_bytes = ch.encode("utf-16-le")
|
|
257
|
+
code_units = [
|
|
258
|
+
int.from_bytes(utf16_bytes[i : i + 2], "little")
|
|
259
|
+
for i in range(0, len(utf16_bytes), 2)
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
for unit in code_units:
|
|
263
|
+
kd = INPUT(type=INPUT_KEYBOARD)
|
|
264
|
+
kd.ki = KEYBDINPUT(wVk=0, wScan=unit, dwFlags=KEYEVENTF_UNICODE, time=0, dwExtraInfo=0)
|
|
265
|
+
ku = INPUT(type=INPUT_KEYBOARD)
|
|
266
|
+
ku.ki = KEYBDINPUT(wVk=0, wScan=unit, dwFlags=KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)
|
|
267
|
+
# Dispatch KeyDown and KeyUp atomically together in a single OS SendInput batch
|
|
268
|
+
_send_inputs([kd, ku])
|
|
269
|
+
time.sleep(0.001)
|
|
251
270
|
|
|
252
271
|
if press_enter:
|
|
272
|
+
time.sleep(0.005)
|
|
253
273
|
kd_enter = INPUT(type=INPUT_KEYBOARD)
|
|
254
274
|
kd_enter.ki = KEYBDINPUT(wVk=win32con.VK_RETURN, wScan=0, dwFlags=0, time=0, dwExtraInfo=0)
|
|
255
275
|
ku_enter = INPUT(type=INPUT_KEYBOARD)
|
|
256
276
|
ku_enter.ki = KEYBDINPUT(wVk=win32con.VK_RETURN, wScan=0, dwFlags=KEYEVENTF_KEYUP, time=0, dwExtraInfo=0)
|
|
257
|
-
|
|
277
|
+
_send_inputs([kd_enter, ku_enter])
|
|
258
278
|
|
|
259
|
-
_send_inputs(inputs)
|
|
260
279
|
_notify_indicator("type")
|
|
261
280
|
|
|
262
281
|
|
|
@@ -63,6 +63,9 @@ BUILTIN_APP_REGISTRY: Dict[str, Dict[str, str]] = {
|
|
|
63
63
|
"mspaint": {"target": "mspaint.exe", "type": "exe", "proc": "mspaint.exe"},
|
|
64
64
|
"photos": {"target": "ms-photos:", "type": "uri", "proc": "Photos.exe"},
|
|
65
65
|
"store": {"target": "ms-windows-store:", "type": "uri", "proc": "WinStore.App.exe"},
|
|
66
|
+
"vlc": {"target": "vlc.exe", "type": "exe", "proc": "vlc.exe"},
|
|
67
|
+
"blender": {"target": "blender.exe", "type": "exe", "proc": "blender.exe"},
|
|
68
|
+
"canva": {"target": "canva.exe", "type": "exe", "proc": "canva.exe"},
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
APP_REGISTRY: Dict[str, Dict[str, str]] = dict(BUILTIN_APP_REGISTRY)
|
|
@@ -511,12 +514,23 @@ def launch_app(
|
|
|
511
514
|
if wait_for_window:
|
|
512
515
|
deadline = time.perf_counter() + timeout
|
|
513
516
|
expected_proc = APP_REGISTRY.get(clean_app, {}).get("proc", "")
|
|
517
|
+
EXCLUDED_SHELL_CLASSES = {
|
|
518
|
+
"progman", "workerw", "shell_traywnd", "shell_secondarytraywnd",
|
|
519
|
+
"windows.ui.core.corewindow", "dv2controlhost",
|
|
520
|
+
}
|
|
521
|
+
EXCLUDED_SHELL_TITLES = {"program manager", "", "taskbar", "start"}
|
|
514
522
|
|
|
515
523
|
while time.perf_counter() < deadline:
|
|
516
524
|
time.sleep(0.1)
|
|
517
525
|
current_windows = list_windows(visible_only=True)
|
|
518
526
|
|
|
519
527
|
for win in current_windows:
|
|
528
|
+
# Universal System Shell Filter: ignore desktop backdrops and taskbars
|
|
529
|
+
win_class = (getattr(win, "class_name", "") or "").lower()
|
|
530
|
+
win_title = (getattr(win, "title", "") or "").strip().lower()
|
|
531
|
+
if win_class in EXCLUDED_SHELL_CLASSES or win_title in EXCLUDED_SHELL_TITLES:
|
|
532
|
+
continue
|
|
533
|
+
|
|
520
534
|
# Prioritize newly created window handles
|
|
521
535
|
if win.hwnd not in existing_hwnds:
|
|
522
536
|
matched_hwnd = win.hwnd
|
|
@@ -114,6 +114,7 @@ CONTROL_TYPE_NAMES: Dict[int, str] = {
|
|
|
114
114
|
INTERACTIVE_TYPES: Set[str] = {
|
|
115
115
|
"Button",
|
|
116
116
|
"Edit",
|
|
117
|
+
"Document",
|
|
117
118
|
"Hyperlink",
|
|
118
119
|
"CheckBox",
|
|
119
120
|
"RadioButton",
|
|
@@ -129,6 +130,7 @@ INTERACTIVE_TYPES: Set[str] = {
|
|
|
129
130
|
UIA_INVOKE_PATTERN_ID = 10000
|
|
130
131
|
UIA_TOGGLE_PATTERN_ID = 10015
|
|
131
132
|
UIA_VALUE_PATTERN_ID = 10002
|
|
133
|
+
UIA_TEXT_PATTERN_ID = 10014
|
|
132
134
|
|
|
133
135
|
|
|
134
136
|
class UIAutomationPlane(AbstractAccessibilityPlane):
|
|
@@ -240,6 +242,31 @@ class UIAutomationPlane(AbstractAccessibilityPlane):
|
|
|
240
242
|
continue
|
|
241
243
|
|
|
242
244
|
name = str(elem.CurrentName or "").strip()
|
|
245
|
+
|
|
246
|
+
# Fallback A: ValuePattern (for Edit / ComboBox controls with empty Name)
|
|
247
|
+
if not name:
|
|
248
|
+
try:
|
|
249
|
+
val_pat = elem.GetCurrentPattern(UIA_VALUE_PATTERN_ID)
|
|
250
|
+
if val_pat:
|
|
251
|
+
val_iface = val_pat.QueryInterface(self._mod.IUIAutomationValuePattern)
|
|
252
|
+
name = str(val_iface.CurrentValue or "").strip()
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
# Fallback B: TextPattern (for Terminal buffers, PowerShell console, and rich text documents)
|
|
257
|
+
if not name and c_type_name in ("Document", "Text", "Edit"):
|
|
258
|
+
try:
|
|
259
|
+
txt_pat = elem.GetCurrentPattern(UIA_TEXT_PATTERN_ID)
|
|
260
|
+
if txt_pat:
|
|
261
|
+
txt_iface = txt_pat.QueryInterface(self._mod.IUIAutomationTextPattern)
|
|
262
|
+
doc_range = txt_iface.DocumentRange
|
|
263
|
+
full_txt = str(doc_range.GetText(300) or "").strip()
|
|
264
|
+
if full_txt:
|
|
265
|
+
lines = [ln.strip() for ln in full_txt.splitlines() if ln.strip()]
|
|
266
|
+
name = " \n ".join(lines[-3:]) if len(lines) > 3 else full_txt
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
|
|
243
270
|
auto_id = str(elem.CurrentAutomationId or "").strip()
|
|
244
271
|
class_name = str(elem.CurrentClassName or "").strip()
|
|
245
272
|
is_enabled = bool(elem.CurrentIsEnabled)
|