screenforge 0.4.0__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.
Files changed (100) hide show
  1. screenforge-0.4.0/LICENSE +21 -0
  2. screenforge-0.4.0/PKG-INFO +43 -0
  3. screenforge-0.4.0/README.md +172 -0
  4. screenforge-0.4.0/cli/__init__.py +0 -0
  5. screenforge-0.4.0/cli/_version.py +1 -0
  6. screenforge-0.4.0/cli/dispatch.py +266 -0
  7. screenforge-0.4.0/cli/doctor.py +487 -0
  8. screenforge-0.4.0/cli/modes/__init__.py +0 -0
  9. screenforge-0.4.0/cli/modes/action.py +262 -0
  10. screenforge-0.4.0/cli/modes/default.py +248 -0
  11. screenforge-0.4.0/cli/modes/demo.py +162 -0
  12. screenforge-0.4.0/cli/modes/dry_run.py +237 -0
  13. screenforge-0.4.0/cli/modes/init.py +133 -0
  14. screenforge-0.4.0/cli/modes/plan.py +148 -0
  15. screenforge-0.4.0/cli/modes/workflow.py +354 -0
  16. screenforge-0.4.0/cli/parser.py +305 -0
  17. screenforge-0.4.0/cli/reporter.py +207 -0
  18. screenforge-0.4.0/cli/session.py +146 -0
  19. screenforge-0.4.0/cli/shared.py +427 -0
  20. screenforge-0.4.0/cli/shorthand.py +90 -0
  21. screenforge-0.4.0/cli/tool_protocol_handlers.py +446 -0
  22. screenforge-0.4.0/common/__init__.py +0 -0
  23. screenforge-0.4.0/common/adapters/__init__.py +21 -0
  24. screenforge-0.4.0/common/adapters/android_adapter.py +273 -0
  25. screenforge-0.4.0/common/adapters/base_adapter.py +24 -0
  26. screenforge-0.4.0/common/adapters/ios_adapter.py +278 -0
  27. screenforge-0.4.0/common/adapters/web_adapter.py +271 -0
  28. screenforge-0.4.0/common/ai.py +277 -0
  29. screenforge-0.4.0/common/ai_autonomous.py +273 -0
  30. screenforge-0.4.0/common/ai_heal.py +222 -0
  31. screenforge-0.4.0/common/cache/__init__.py +15 -0
  32. screenforge-0.4.0/common/cache/cache_hash.py +57 -0
  33. screenforge-0.4.0/common/cache/cache_manager.py +300 -0
  34. screenforge-0.4.0/common/cache/cache_stats.py +133 -0
  35. screenforge-0.4.0/common/cache/cache_storage.py +79 -0
  36. screenforge-0.4.0/common/cache/embedding_loader.py +150 -0
  37. screenforge-0.4.0/common/capabilities.py +121 -0
  38. screenforge-0.4.0/common/case_memory.py +327 -0
  39. screenforge-0.4.0/common/error_codes.py +61 -0
  40. screenforge-0.4.0/common/exceptions.py +18 -0
  41. screenforge-0.4.0/common/executor.py +1504 -0
  42. screenforge-0.4.0/common/failure_diagnosis.py +138 -0
  43. screenforge-0.4.0/common/history_manager.py +75 -0
  44. screenforge-0.4.0/common/logs.py +168 -0
  45. screenforge-0.4.0/common/mcp_server.py +467 -0
  46. screenforge-0.4.0/common/preflight.py +496 -0
  47. screenforge-0.4.0/common/progress.py +37 -0
  48. screenforge-0.4.0/common/run_reporter.py +415 -0
  49. screenforge-0.4.0/common/run_resume.py +149 -0
  50. screenforge-0.4.0/common/runtime_modes.py +35 -0
  51. screenforge-0.4.0/common/tool_protocol.py +196 -0
  52. screenforge-0.4.0/common/visual_fallback.py +71 -0
  53. screenforge-0.4.0/common/workflow_schema.py +150 -0
  54. screenforge-0.4.0/config/__init__.py +0 -0
  55. screenforge-0.4.0/config/config.py +167 -0
  56. screenforge-0.4.0/config/env_loader.py +76 -0
  57. screenforge-0.4.0/pyproject.toml +107 -0
  58. screenforge-0.4.0/screenforge.egg-info/PKG-INFO +43 -0
  59. screenforge-0.4.0/screenforge.egg-info/SOURCES.txt +98 -0
  60. screenforge-0.4.0/screenforge.egg-info/dependency_links.txt +1 -0
  61. screenforge-0.4.0/screenforge.egg-info/entry_points.txt +2 -0
  62. screenforge-0.4.0/screenforge.egg-info/requires.txt +37 -0
  63. screenforge-0.4.0/screenforge.egg-info/top_level.txt +4 -0
  64. screenforge-0.4.0/setup.cfg +4 -0
  65. screenforge-0.4.0/tests/test_ai_autonomous.py +139 -0
  66. screenforge-0.4.0/tests/test_ai_brain.py +106 -0
  67. screenforge-0.4.0/tests/test_ai_heal.py +45 -0
  68. screenforge-0.4.0/tests/test_android_smoke_live.py +379 -0
  69. screenforge-0.4.0/tests/test_cache_manager.py +94 -0
  70. screenforge-0.4.0/tests/test_capabilities.py +98 -0
  71. screenforge-0.4.0/tests/test_cli_action_json.py +51 -0
  72. screenforge-0.4.0/tests/test_codegen_quality.py +333 -0
  73. screenforge-0.4.0/tests/test_dispatch.py +96 -0
  74. screenforge-0.4.0/tests/test_doctor_orphan_browser.py +147 -0
  75. screenforge-0.4.0/tests/test_error_codes.py +31 -0
  76. screenforge-0.4.0/tests/test_executor.py +604 -0
  77. screenforge-0.4.0/tests/test_failure_diagnosis.py +98 -0
  78. screenforge-0.4.0/tests/test_interaction_actions.py +230 -0
  79. screenforge-0.4.0/tests/test_ios_smoke_live.py +353 -0
  80. screenforge-0.4.0/tests/test_mcp_ref_cache.py +90 -0
  81. screenforge-0.4.0/tests/test_ml_optional.py +72 -0
  82. screenforge-0.4.0/tests/test_parser.py +146 -0
  83. screenforge-0.4.0/tests/test_run_reporter.py +220 -0
  84. screenforge-0.4.0/tests/test_run_resume.py +165 -0
  85. screenforge-0.4.0/tests/test_runtime_modes.py +56 -0
  86. screenforge-0.4.0/tests/test_screenshot_annotator.py +47 -0
  87. screenforge-0.4.0/tests/test_shorthand.py +124 -0
  88. screenforge-0.4.0/tests/test_tool_protocol_diagnosis.py +29 -0
  89. screenforge-0.4.0/tests/test_utils_ios.py +362 -0
  90. screenforge-0.4.0/tests/test_utils_web.py +38 -0
  91. screenforge-0.4.0/tests/test_utils_xml.py +365 -0
  92. screenforge-0.4.0/tests/test_visual_fallback.py +43 -0
  93. screenforge-0.4.0/tests/test_web_adapter.py +128 -0
  94. screenforge-0.4.0/tests/test_web_dom_complex_live.py +737 -0
  95. screenforge-0.4.0/tests/test_web_smoke_live.py +547 -0
  96. screenforge-0.4.0/utils/__init__.py +0 -0
  97. screenforge-0.4.0/utils/screenshot_annotator.py +60 -0
  98. screenforge-0.4.0/utils/utils_ios.py +195 -0
  99. screenforge-0.4.0/utils/utils_web.py +304 -0
  100. screenforge-0.4.0/utils/utils_xml.py +218 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jhinzzz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: screenforge
3
+ Version: 0.4.0
4
+ Summary: AI-driven cross-platform UI automation engine with test script generation
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/jhinzzz/ScreenForge
7
+ Project-URL: Repository, https://github.com/jhinzzz/ScreenForge
8
+ Project-URL: Issues, https://github.com/jhinzzz/ScreenForge/issues
9
+ Requires-Python: >=3.11
10
+ License-File: LICENSE
11
+ Requires-Dist: playwright>=1.50
12
+ Requires-Dist: openai>=2.0
13
+ Requires-Dist: allure-pytest>=2.15
14
+ Requires-Dist: loguru>=0.7
15
+ Requires-Dist: pydantic>=2.0
16
+ Requires-Dist: python-dotenv>=1.0
17
+ Requires-Dist: PyYAML>=6.0
18
+ Requires-Dist: pillow>=12.0
19
+ Requires-Dist: lxml>=5.0
20
+ Requires-Dist: requests>=2.30
21
+ Requires-Dist: rich>=14.0
22
+ Requires-Dist: typer>=0.24
23
+ Requires-Dist: retry2>=0.9
24
+ Requires-Dist: opencv-python>=4.10
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: filelock>=3.12
27
+ Provides-Extra: android
28
+ Requires-Dist: uiautomator2>=3.5; extra == "android"
29
+ Requires-Dist: adbutils>=2.12; extra == "android"
30
+ Provides-Extra: ios
31
+ Requires-Dist: facebook-wda>=1.0; extra == "ios"
32
+ Provides-Extra: ml
33
+ Requires-Dist: torch>=2.0; extra == "ml"
34
+ Requires-Dist: transformers>=5.0; extra == "ml"
35
+ Requires-Dist: sentence-transformers>=5.0; extra == "ml"
36
+ Requires-Dist: scikit-learn>=1.8; extra == "ml"
37
+ Provides-Extra: playground
38
+ Requires-Dist: fastapi>=0.115; extra == "playground"
39
+ Requires-Dist: uvicorn>=0.34; extra == "playground"
40
+ Requires-Dist: websockets>=14.0; extra == "playground"
41
+ Provides-Extra: all
42
+ Requires-Dist: screenforge[android,ios,ml,playground]; extra == "all"
43
+ Dynamic: license-file
@@ -0,0 +1,172 @@
1
+ # ScreenForge
2
+
3
+ [![CI](https://github.com/jhinzzz/ScreenForge/actions/workflows/ci.yml/badge.svg)](https://github.com/jhinzzz/ScreenForge/actions/workflows/ci.yml)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+
7
+ **[中文](./README_CN.md)** | English
8
+
9
+ > Describe what to test. Watch it happen. Get a pytest script.
10
+
11
+ ScreenForge is an AI-driven UI automation engine that turns natural language into executable test scripts. Unlike record-and-replay tools, you don't perform the actions yourself — the AI does it for you.
12
+
13
+ ![ScreenForge Demo](docs/assets/demo.gif)
14
+
15
+ ## Why ScreenForge?
16
+
17
+ | | Playwright Codegen | Browser Use | Midscene.js | **ScreenForge** |
18
+ |---|---|---|---|---|
19
+ | Need to perform actions yourself? | Yes | No | No | **No** |
20
+ | Generates replayable test scripts? | Yes | No | No | **Yes (pytest)** |
21
+ | Self-healing when UI changes? | No | No | No | **Yes** |
22
+ | Works as AI Agent tool (MCP)? | No | Yes | No | **Yes** |
23
+
24
+ **Core architecture**: Your AI Agent is the brain (understands requirements, makes decisions). ScreenForge is the hands (executes UI actions, generates code).
25
+
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ pip install screenforge
30
+
31
+ # See the magic without any API key:
32
+ screenforge --demo
33
+
34
+ # For real usage, set your LLM key:
35
+ export OPENAI_API_KEY=sk-...
36
+
37
+ # Inspect the current page (returns DOM tree for your Agent to analyze):
38
+ echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
39
+
40
+ # Execute a single action:
41
+ screenforge --action click --platform web --locator-type text --locator-value "Login"
42
+ ```
43
+
44
+ ## How It Works
45
+
46
+ ```
47
+ You (or your AI Agent) ScreenForge
48
+ │ │
49
+ ├──── "Test the login" ─────►│
50
+ │ ├── inspect_ui (get DOM tree)
51
+ │◄── DOM tree ──────────────┤
52
+ │ │
53
+ ├──── click #email ─────────►│
54
+ ├──── input "user@..." ─────►│
55
+ ├──── click "Sign In" ──────►│
56
+ │ │
57
+ │◄── pytest script ─────────┤
58
+ │◄── Allure report ─────────┤
59
+ ```
60
+
61
+ Each step: **inspect → decide → act → verify**. The AI decides, ScreenForge executes.
62
+
63
+ ## Features
64
+
65
+ - **Cross-platform**: Android (uiautomator2), iOS (wda), Web (Playwright)
66
+ - **Self-healing engine**: When tests break due to UI changes, the engine auto-repairs locators with confidence scoring and AST validation
67
+ - **L1/L2 semantic cache**: Same page + same instruction = instant response, no LLM call needed
68
+ - **Visual fallback**: When DOM can't locate elements (Canvas, games), VLM parses screenshots
69
+ - **MCP server**: Any MCP-compatible Agent can drive ScreenForge natively
70
+ - **Structured output**: JSON Lines events + `report/runs/<id>/` artifacts for CI integration
71
+
72
+ ## Agent Integration (Claude Code / Cursor / Codex)
73
+
74
+ ScreenForge exposes itself as a tool for AI Agents. The standard loop:
75
+
76
+ ```bash
77
+ # 1. Get page structure (your Agent analyzes it)
78
+ echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
79
+
80
+ # 2. Your Agent decides what to do, sends precise actions
81
+ screenforge --action click --platform web --locator-type text --locator-value "Login"
82
+
83
+ # 3. Verify the result, repeat
84
+ echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
85
+ ```
86
+
87
+ For batch operations, use workflows:
88
+
89
+ ```bash
90
+ screenforge --workflow ./workflows/login.yaml --platform web --json
91
+ ```
92
+
93
+ Or start the MCP server for native Agent integration:
94
+
95
+ ```bash
96
+ screenforge --mcp-server
97
+ ```
98
+
99
+ ## GitHub Actions
100
+
101
+ Add ScreenForge to your CI pipeline:
102
+
103
+ ```yaml
104
+ - uses: jhinzzz/ScreenForge@v1
105
+ with:
106
+ platform: web
107
+ workflow: ./workflows/login.yaml
108
+ openai-api-key: ${{ secrets.OPENAI_API_KEY }}
109
+ ```
110
+
111
+ Results are auto-uploaded as Allure artifacts. See [action.yml](action.yml) for all inputs.
112
+
113
+ See [Agent Integration Guide](docs/agent_guide.md) for the complete protocol.
114
+
115
+ ## Installation (from source)
116
+
117
+ ```bash
118
+ git clone https://github.com/jhinzzz/ScreenForge.git
119
+ cd ScreenForge
120
+ python -m venv .venv && source .venv/bin/activate
121
+ pip install -e ".[all]"
122
+ ```
123
+
124
+ Platform-specific extras:
125
+ - Web only: `pip install -e .` (default, includes Playwright)
126
+ - Android: `pip install -e ".[android]"`
127
+ - iOS: `pip install -e ".[ios]"`
128
+ - ML/cache: `pip install -e ".[ml]"` (sentence-transformers for semantic cache)
129
+
130
+ ## Configuration
131
+
132
+ ```bash
133
+ # Required: LLM API key (OpenAI-compatible endpoint)
134
+ export OPENAI_API_KEY=sk-...
135
+
136
+ # Optional: custom endpoint (defaults to api.openai.com)
137
+ export OPENAI_BASE_URL=https://api.openai.com/v1
138
+
139
+ # Optional: model (defaults to gpt-4o)
140
+ export MODEL_NAME=gpt-4o
141
+ ```
142
+
143
+ Or create a `.env` file (copy from `.env_template`).
144
+
145
+ ## Badge
146
+
147
+ If ScreenForge generates tests for your project, add this badge to your README:
148
+
149
+ ```markdown
150
+ [![Tests by ScreenForge](https://img.shields.io/badge/tests%20by-ScreenForge-blue?logo=pytest)](https://github.com/jhinzzz/ScreenForge)
151
+ ```
152
+
153
+ [![Tests by ScreenForge](https://img.shields.io/badge/tests%20by-ScreenForge-blue?logo=pytest)](https://github.com/jhinzzz/ScreenForge)
154
+
155
+ ## Learn More
156
+
157
+ | Resource | Description |
158
+ |----------|-------------|
159
+ | [Mobile Setup](docs/mobile-setup.md) | Android & iOS device connection guide |
160
+ | [MCP Setup (3 min)](docs/mcp-setup.md) | Connect to Claude Desktop / Cursor / Cline / Claude Code |
161
+ | [Agent Guide](docs/agent_guide.md) | Integration protocol for AI Agents |
162
+ | [Capability Matrix](docs/capability-matrix.md) | Supported platforms, actions, and locators |
163
+ | [Workflow Examples](docs/workflows/) | YAML workflow templates |
164
+ | [CHANGELOG](CHANGELOG.md) | Version history |
165
+
166
+ ## Contributing
167
+
168
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Issues and PRs welcome!
169
+
170
+ ## License
171
+
172
+ [MIT](LICENSE)
File without changes
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -0,0 +1,266 @@
1
+ """Execution dispatcher and CLI entry point."""
2
+
3
+ import sys
4
+
5
+ from cli.doctor import run_capabilities_mode, run_doctor_mode
6
+ from cli.modes.action import run_action_default_mode
7
+ from cli.modes.default import run_default_mode
8
+ from cli.modes.dry_run import run_action_dry_run_mode, run_dry_run_mode
9
+ from cli.modes.plan import run_action_plan_only_mode, run_plan_only_mode
10
+ from cli.modes.workflow import (
11
+ run_workflow_default_mode,
12
+ run_workflow_dry_run_mode,
13
+ run_workflow_plan_only_mode,
14
+ )
15
+ from cli.parser import build_parser, validate_cli_args
16
+ from cli.reporter import _load_context_content, _resolve_output_script_path
17
+ from cli.shared import _SharedAdapterManager, config, log
18
+ from cli.tool_protocol_handlers import (
19
+ _requires_model_runtime,
20
+ run_mcp_server_mode,
21
+ run_tool_request_mode,
22
+ run_tool_stdin_mode,
23
+ )
24
+ from common.run_resume import RunContextLoadError
25
+ from common.runtime_modes import (
26
+ MODE_DOCTOR,
27
+ MODE_DRY_RUN,
28
+ MODE_PLAN_ONLY,
29
+ resolve_execution_mode,
30
+ )
31
+
32
+
33
+ def _dispatch_execution(
34
+ args,
35
+ execution_mode: str,
36
+ output_script_path: str,
37
+ context_content: str,
38
+ resume_context: dict,
39
+ shared_adapter_manager: _SharedAdapterManager | None = None,
40
+ ) -> int:
41
+ if execution_mode == MODE_DOCTOR:
42
+ return run_doctor_mode(args, output_script_path)
43
+ if args.workflow and execution_mode == MODE_PLAN_ONLY:
44
+ return run_workflow_plan_only_mode(
45
+ args,
46
+ output_script_path,
47
+ resume_context,
48
+ )
49
+ if args.workflow and execution_mode == MODE_DRY_RUN:
50
+ return run_workflow_dry_run_mode(
51
+ args,
52
+ output_script_path,
53
+ resume_context,
54
+ )
55
+ if args.workflow:
56
+ return run_workflow_default_mode(
57
+ args,
58
+ output_script_path,
59
+ resume_context,
60
+ )
61
+ if args.action and execution_mode == MODE_PLAN_ONLY:
62
+ return run_action_plan_only_mode(
63
+ args,
64
+ output_script_path,
65
+ resume_context,
66
+ )
67
+ if args.action and execution_mode == MODE_DRY_RUN:
68
+ return run_action_dry_run_mode(
69
+ args,
70
+ output_script_path,
71
+ resume_context,
72
+ )
73
+ if args.action:
74
+ return run_action_default_mode(
75
+ args,
76
+ output_script_path,
77
+ resume_context,
78
+ shared_adapter_manager=shared_adapter_manager,
79
+ )
80
+ if execution_mode == MODE_PLAN_ONLY:
81
+ return run_plan_only_mode(
82
+ args,
83
+ output_script_path,
84
+ context_content,
85
+ resume_context,
86
+ )
87
+ if execution_mode == MODE_DRY_RUN:
88
+ return run_dry_run_mode(
89
+ args,
90
+ output_script_path,
91
+ context_content,
92
+ resume_context,
93
+ )
94
+ return run_default_mode(
95
+ args,
96
+ output_script_path,
97
+ context_content,
98
+ resume_context,
99
+ )
100
+
101
+
102
+ def _run_session_end(args) -> int:
103
+ from cli.session import delete_session, load_session, stop_session_recording
104
+
105
+ session_id = args.session_end
106
+ session = load_session(session_id)
107
+ if not session:
108
+ log.error(f"❌ Session not found: {session_id}")
109
+ return 1
110
+
111
+ output_path = session["output_path"]
112
+
113
+ video_path = stop_session_recording(session_id)
114
+ if video_path:
115
+ log.info(f"🎬 [Session] Recording saved: {video_path}")
116
+
117
+ delete_session(session_id)
118
+ log.info(f"✅ [Session] Ended session '{session_id}'")
119
+ log.info(f"📄 [Session] Test script: {output_path}")
120
+ log.info(f"📊 [Session] Total steps: {session.get('steps', 0)}")
121
+ return 0
122
+
123
+
124
+ def main():
125
+ from cli.shorthand import preprocess_argv
126
+
127
+ processed_argv = preprocess_argv(sys.argv)
128
+
129
+ parser = build_parser()
130
+ args = parser.parse_args(processed_argv[1:])
131
+
132
+ if getattr(args, "session_end", ""):
133
+ sys.exit(_run_session_end(args))
134
+
135
+ if getattr(args, "web_stop", False):
136
+ from common.adapters.web_adapter import stop_persistent_browser
137
+
138
+ # Idempotent: stopping a browser, or finding none to stop, both succeed.
139
+ stop_persistent_browser()
140
+ sys.exit(0)
141
+
142
+ try:
143
+ validate_cli_args(args)
144
+ except ValueError as e:
145
+ log.error(f"[E010] Invalid CLI arguments: {e}. Fix: run 'screenforge --help' to see valid options")
146
+ sys.exit(2)
147
+
148
+ if getattr(args, "init", False):
149
+ from cli.modes.init import run_init_mode
150
+
151
+ sys.exit(run_init_mode())
152
+
153
+ if getattr(args, "demo", False):
154
+ from cli.modes.demo import run_demo_mode
155
+
156
+ sys.exit(run_demo_mode())
157
+
158
+ if getattr(args, "playground", False):
159
+ try:
160
+ from playground.app import run_server
161
+ except ImportError:
162
+ log.error("[E013] Playground requires extra dependencies. Fix: pip install screenforge[playground]")
163
+ sys.exit(1)
164
+ cdp_url = "http://127.0.0.1:9333"
165
+ log.info(f"Starting Playground on http://127.0.0.1:{args.playground_port}")
166
+ log.info(f"CDP screencast target: {cdp_url}")
167
+ run_server(port=args.playground_port, cdp_url=cdp_url)
168
+ sys.exit(0)
169
+
170
+ if args.tool_stdin:
171
+ from common.progress import set_tool_mode
172
+ set_tool_mode(True)
173
+ if sys.stdin.isatty():
174
+ import io
175
+ import json as _json
176
+ sys.stdin = io.StringIO(_json.dumps({"operation": "inspect_ui", "platform": args.platform}))
177
+ sys.exit(run_tool_stdin_mode(args))
178
+
179
+ if args.mcp_server:
180
+ from common.progress import set_tool_mode
181
+ set_tool_mode(True)
182
+ sys.exit(run_mcp_server_mode(args))
183
+
184
+ if args.tool_request:
185
+ from common.progress import set_tool_mode
186
+ set_tool_mode(True)
187
+ sys.exit(run_tool_request_mode(args))
188
+
189
+ if args.capabilities:
190
+ sys.exit(run_capabilities_mode(args))
191
+
192
+ execution_mode = resolve_execution_mode(
193
+ doctor=args.doctor,
194
+ plan_only=args.plan_only,
195
+ dry_run=args.dry_run,
196
+ )
197
+
198
+ session_id = getattr(args, "session_id", "")
199
+ shared_adapter_mgr = None
200
+ if session_id and args.action:
201
+ from cli.session import (
202
+ create_session,
203
+ load_session,
204
+ resolve_session_output_path,
205
+ update_session,
206
+ )
207
+
208
+ session = load_session(session_id)
209
+ if session:
210
+ output_script_path = session["output_path"]
211
+ shared_adapter_mgr = _SharedAdapterManager()
212
+ else:
213
+ from cli.session import start_session_recording
214
+
215
+ output_script_path = resolve_session_output_path(session_id, args.platform)
216
+ session = create_session(session_id, args.platform, output_script_path)
217
+ shared_adapter_mgr = _SharedAdapterManager()
218
+ start_session_recording(session_id, args.platform)
219
+ else:
220
+ output_script_path = _resolve_output_script_path(args)
221
+
222
+ log.info("=" * 60)
223
+ log.info("Starting ScreenForge UI automation engine")
224
+ target_label = (
225
+ args.goal
226
+ or getattr(args, "action_name", "")
227
+ or getattr(args, "action", "")
228
+ or args.workflow
229
+ or "doctor / no-goal mode"
230
+ )
231
+ log.info(f"Target: {target_label}")
232
+ log.info(f"Circuit breaker: max {args.max_retries} retries per step")
233
+ log.info(
234
+ f"Platform: {args.platform} | Vision: {'on' if args.vision else 'off'}"
235
+ )
236
+ log.info(f"Mode: {execution_mode}")
237
+ log.info(f"Output: {output_script_path}")
238
+ log.info("=" * 60)
239
+
240
+ try:
241
+ context_content, resume_context = _load_context_content(args)
242
+ except RunContextLoadError as e:
243
+ log.error(f"[E011] Failed to restore run context: {e}. Fix: check that report/runs/<run_id>/ exists and contains summary.json")
244
+ sys.exit(2)
245
+
246
+ if _requires_model_runtime(args, execution_mode) and not config.validate_config():
247
+ log.error("[E012] Configuration validation failed. See errors above for details and fix instructions")
248
+ sys.exit(1)
249
+
250
+ exit_code = _dispatch_execution(
251
+ args,
252
+ execution_mode,
253
+ output_script_path,
254
+ context_content,
255
+ resume_context,
256
+ shared_adapter_manager=shared_adapter_mgr,
257
+ )
258
+
259
+ if session_id and args.action:
260
+ from cli.session import load_session, update_session
261
+
262
+ session = load_session(session_id)
263
+ if session and exit_code == 0:
264
+ update_session(session_id, steps=session.get("steps", 0) + 1)
265
+
266
+ sys.exit(exit_code)