webpilot-engine 2.0.0__py3-none-any.whl

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 (47) hide show
  1. adapters/__init__.py +14 -0
  2. adapters/base.py +34 -0
  3. adapters/browser_adapter.py +300 -0
  4. adapters/dom_scripts.py +518 -0
  5. adapters/envelope_builder.py +108 -0
  6. adapters/live_session_manager.py +326 -0
  7. adapters/upload_adapter.py +77 -0
  8. builder.py +83 -0
  9. cli.py +341 -0
  10. config/__init__.py +23 -0
  11. config/settings.py +339 -0
  12. constants/__init__.py +165 -0
  13. constants/contracts.py +95 -0
  14. constants/selectors.py +27 -0
  15. constants/timeouts.py +25 -0
  16. core/__init__.py +45 -0
  17. core/exceptions.py +33 -0
  18. core/models.py +273 -0
  19. engine.py +199 -0
  20. exceptions.py +23 -0
  21. mcp_server.py +347 -0
  22. models.py +29 -0
  23. orchestration/__init__.py +19 -0
  24. orchestration/context.py +71 -0
  25. orchestration/flow_orchestrator.py +358 -0
  26. orchestration/session_coordinator.py +88 -0
  27. services/__init__.py +17 -0
  28. services/auth_navigation_service.py +129 -0
  29. services/field_interaction_service.py +174 -0
  30. services/form_filler_service.py +106 -0
  31. services/inspection_formatter_service.py +68 -0
  32. services/inspection_service.py +40 -0
  33. services/reactive_interaction_service.py +174 -0
  34. supervisor/__init__.py +13 -0
  35. supervisor/client.py +171 -0
  36. supervisor/contracts.py +62 -0
  37. supervisor/master_daemon.py +303 -0
  38. supervisor/shell.py +194 -0
  39. supervisor/worker_process.py +400 -0
  40. validators/__init__.py +9 -0
  41. validators/form_validator.py +28 -0
  42. validators/payload_validator.py +25 -0
  43. webpilot_engine-2.0.0.dist-info/METADATA +299 -0
  44. webpilot_engine-2.0.0.dist-info/RECORD +47 -0
  45. webpilot_engine-2.0.0.dist-info/WHEEL +5 -0
  46. webpilot_engine-2.0.0.dist-info/entry_points.txt +6 -0
  47. webpilot_engine-2.0.0.dist-info/top_level.txt +14 -0
cli.py ADDED
@@ -0,0 +1,341 @@
1
+ """WebPilot: Autonomous Multi-Tier Browser Automation Engine & AI Agent Web Runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import sys
9
+ from typing import Any, Dict
10
+
11
+ from orchestration.context import ApplyRequestContext, InspectionRequestContext
12
+ from orchestration.flow_orchestrator import FlowOrchestrator
13
+ from services.inspection_formatter_service import InspectionFormatterService
14
+ from supervisor.client import DEFAULT_SUPERVISOR_URL, SupervisorClient
15
+ from supervisor.contracts import SupervisorActionRequest, SupervisorActionResponse
16
+
17
+
18
+ def print_telemetry_banner(resp: SupervisorActionResponse) -> None:
19
+ """Renders real-time process tree telemetry across Layer 1, 2, and 3."""
20
+ if resp.browser_pid or resp.worker_pid or resp.active_url:
21
+ print("\n" + "═" * 70)
22
+ print(" 🚀 WEBPILOT | AUTONOMOUS BROWSER RUNTIME & SUPERVISOR TELEMETRY")
23
+ print("═" * 70)
24
+ if resp.worker_pid:
25
+ print(f" • Layer 2 Worker PID : {resp.worker_pid}")
26
+ if resp.browser_pid:
27
+ print(f" • Layer 3 Chromium PID : {resp.browser_pid}")
28
+ if resp.active_tab_index is not None:
29
+ print(f" • Active Tab Index : Tab #{resp.active_tab_index} (Total: {resp.total_tabs})")
30
+ if resp.active_title:
31
+ print(f" • Active Page Title : {resp.active_title}")
32
+ if resp.active_url:
33
+ print(f" • Active Page URL : {resp.active_url}")
34
+ print("═" * 70)
35
+
36
+
37
+ def get_base_url(args: argparse.Namespace) -> str:
38
+ host = getattr(args, "host", None) or "127.0.0.1"
39
+ port = getattr(args, "port", None) or 9333
40
+ return f"http://{host}:{port}"
41
+
42
+
43
+ def inspect_cmd(args: argparse.Namespace) -> None:
44
+ """Executes the universal inspection workflow."""
45
+ if getattr(args, "standalone", False):
46
+ req = InspectionRequestContext(
47
+ url=args.url,
48
+ output_path=args.output,
49
+ screenshot_path=args.screenshot,
50
+ cookies_path=args.cookies,
51
+ unpack_options=args.unpack_options,
52
+ headed=args.headed,
53
+ config_path=args.config,
54
+ device_profile_path=args.device_profile,
55
+ env_file=args.env_file,
56
+ keep_alive=args.keep_alive,
57
+ close_session=args.close_session,
58
+ list_tabs=args.list_tabs,
59
+ tab_index=args.tab,
60
+ timeout_minutes=args.timeout_minutes,
61
+ )
62
+ FlowOrchestrator.execute_inspection_flow(req)
63
+ return
64
+
65
+ # Multi-tier Master Supervisor execution (default)
66
+ client = SupervisorClient(base_url=get_base_url(args))
67
+
68
+ if args.close_session:
69
+ res = client.stop_service()
70
+ print(f"[✓] {res.message}")
71
+ return
72
+
73
+ if args.list_tabs:
74
+ resp = client.execute(SupervisorActionRequest(action="list_tabs"))
75
+ print("\n" + "═" * 70)
76
+ print(" 📑 OPEN BROWSER TABS")
77
+ print("═" * 70)
78
+ if not resp.tabs:
79
+ print(" [!] No open tabs detected in current session.")
80
+ else:
81
+ for t in resp.tabs:
82
+ active_mark = " [*ACTIVE*]" if t.get("is_active") else ""
83
+ print(f" • Tab #{t['index']}{active_mark}: '{t['title']}'\n URL: {t['url']}")
84
+ print("═" * 70 + "\n")
85
+ return
86
+
87
+ req = SupervisorActionRequest(
88
+ action="inspect",
89
+ url=args.url,
90
+ cookies_path=args.cookies,
91
+ screenshot_path=args.screenshot,
92
+ tab_index=args.tab,
93
+ unpack_options=args.unpack_options,
94
+ headed=args.headed,
95
+ timeout_minutes=args.timeout_minutes,
96
+ )
97
+ resp = client.execute(req)
98
+ print_telemetry_banner(resp)
99
+ for line in resp.output_lines:
100
+ print(line)
101
+
102
+ if resp.schema:
103
+ if args.output:
104
+ with open(args.output, "w", encoding="utf-8") as f:
105
+ json.dump(resp.schema, f, indent=2, ensure_ascii=False)
106
+ print(f"[✓] Full schema successfully saved to: {args.output}")
107
+ summary = InspectionFormatterService.format_summary(resp.schema)
108
+ print("\n" + summary)
109
+
110
+ if not resp.success:
111
+ print(f"[!] Inspection failed: {resp.message}")
112
+ sys.exit(1)
113
+
114
+
115
+ def apply_cmd(args: argparse.Namespace) -> None:
116
+ """Executes the universal form filling and submission workflow."""
117
+ auto_inspect = not getattr(args, "no_auto_inspect", False)
118
+
119
+ if getattr(args, "standalone", False):
120
+ req = ApplyRequestContext(
121
+ url=args.url,
122
+ data_path=args.data,
123
+ fill_arguments=args.fill or [],
124
+ press_buttons=args.press or [],
125
+ upload_files=args.upload or [],
126
+ cookies_path=args.cookies,
127
+ submit=args.submit,
128
+ screenshot_path=args.screenshot,
129
+ headed=args.headed,
130
+ config_path=args.config,
131
+ device_profile_path=args.device_profile,
132
+ env_file=args.env_file,
133
+ keep_alive=args.keep_alive,
134
+ close_session=args.close_session,
135
+ list_tabs=args.list_tabs,
136
+ tab_index=args.tab,
137
+ timeout_minutes=args.timeout_minutes,
138
+ auto_inspect=auto_inspect,
139
+ )
140
+ FlowOrchestrator.execute_apply_flow(req)
141
+ return
142
+
143
+ # Multi-tier Master Supervisor execution (default)
144
+ client = SupervisorClient(base_url=get_base_url(args))
145
+
146
+ if args.close_session:
147
+ res = client.stop_service()
148
+ print(f"[✓] {res.message}")
149
+ return
150
+
151
+ if args.list_tabs:
152
+ resp = client.execute(SupervisorActionRequest(action="list_tabs"))
153
+ print("\n" + "═" * 70)
154
+ print(" 📑 OPEN BROWSER TABS")
155
+ print("═" * 70)
156
+ if not resp.tabs:
157
+ print(" [!] No open tabs detected in current session.")
158
+ else:
159
+ for t in resp.tabs:
160
+ active_mark = " [*ACTIVE*]" if t.get("is_active") else ""
161
+ print(f" • Tab #{t['index']}{active_mark}: '{t['title']}'\n URL: {t['url']}")
162
+ print("═" * 70 + "\n")
163
+ return
164
+
165
+ req = SupervisorActionRequest(
166
+ action="apply",
167
+ url=args.url,
168
+ data_path=args.data,
169
+ fill_arguments=args.fill or [],
170
+ press_buttons=args.press or [],
171
+ upload_files=args.upload or [],
172
+ cookies_path=args.cookies,
173
+ submit=args.submit,
174
+ screenshot_path=args.screenshot,
175
+ headed=args.headed,
176
+ auto_inspect=auto_inspect,
177
+ tab_index=args.tab,
178
+ timeout_minutes=args.timeout_minutes,
179
+ )
180
+ resp = client.execute(req)
181
+ print_telemetry_banner(resp)
182
+ for line in resp.output_lines:
183
+ print(line)
184
+
185
+ if resp.schema and auto_inspect:
186
+ summary = InspectionFormatterService.format_summary(resp.schema)
187
+ print("\n" + summary)
188
+
189
+ if not resp.success:
190
+ print(f"[!] Apply operation failed: {resp.message}")
191
+ sys.exit(1)
192
+
193
+
194
+ def service_cmd(args: argparse.Namespace) -> None:
195
+ """Manages the Master Supervisor daemon (status, restart, stop, start)."""
196
+ client = SupervisorClient(base_url=get_base_url(args))
197
+ action = args.service_action.lower()
198
+
199
+ if action == "status":
200
+ status_info = client.get_status()
201
+ print("\n" + "═" * 70)
202
+ print(" 🛡️ MASTER SUPERVISOR & MULTI-TIER DAEMON STATUS")
203
+ print("═" * 70)
204
+ if not status_info.get("supervisor_alive"):
205
+ print(" • Layer 1 Supervisor : ⭕ STOPPED / INACTIVE")
206
+ print(" • Layer 2 Worker : ⭕ INACTIVE")
207
+ print(" • Layer 3 Chromium : ⭕ INACTIVE")
208
+ else:
209
+ print(f" • Layer 1 Supervisor : 🟢 RUNNING (PID: {status_info.get('supervisor_pid')}, Port: {status_info.get('supervisor_port')})")
210
+ worker_alive = status_info.get("worker_alive", False)
211
+ worker_mark = "🟢 RUNNING" if worker_alive else "⚪ IDLE"
212
+ print(f" • Layer 2 Worker : {worker_mark} (PID: {status_info.get('worker_pid') or 'None'})")
213
+ print(f" • Watchdog Inactivity : {status_info.get('idle_seconds', 0)}s idle / {status_info.get('inactivity_timeout_seconds')}s limit")
214
+
215
+ if worker_alive:
216
+ worker_resp = client.execute(SupervisorActionRequest(action="status"))
217
+ if worker_resp.browser_pid:
218
+ print(f" • Layer 3 Chromium : 🟢 RUNNING (PID: {worker_resp.browser_pid})")
219
+ print(f" • Active Page : {worker_resp.active_title} ({worker_resp.active_url})")
220
+ print(f" • Open Tabs Count : {worker_resp.total_tabs}")
221
+ else:
222
+ print(" • Layer 3 Chromium : ⚪ NOT SPAWNED YET")
223
+ print("═" * 70 + "\n")
224
+
225
+ elif action == "restart":
226
+ res = client.restart_worker()
227
+ print(f"[*] {res.message}")
228
+
229
+ elif action == "stop":
230
+ res = client.stop_service()
231
+ print(f"[✓] {res.message}")
232
+
233
+ elif action == "start":
234
+ client.ensure_running()
235
+ print("[✓] Master Supervisor is active and listening.")
236
+
237
+ else:
238
+ print(f"[!] Unknown service action: '{action}'. Use status, restart, stop, or start.")
239
+
240
+
241
+ def shell_cmd(args: argparse.Namespace) -> None:
242
+ """Launches the interactive REPL shell."""
243
+ from supervisor.shell import run_interactive_shell
244
+ run_interactive_shell(host=args.host, port=args.port)
245
+
246
+
247
+ def main():
248
+ if "--run-daemon" in sys.argv:
249
+ from supervisor.master_daemon import run_master_daemon
250
+ idx = sys.argv.index("--run-daemon")
251
+ port = 9333
252
+ if len(sys.argv) > idx + 1 and sys.argv[idx + 1].isdigit():
253
+ port = int(sys.argv[idx + 1])
254
+ run_master_daemon(port=port)
255
+ return
256
+
257
+ if "--run-worker" in sys.argv:
258
+ from supervisor.worker_process import run_worker_loop
259
+ run_worker_loop()
260
+ return
261
+
262
+ parser = argparse.ArgumentParser(
263
+ prog="webpilot",
264
+ description="WebPilot: Autonomous Multi-Tier Browser Automation Engine & AI Agent Web Runtime",
265
+ )
266
+ parser.add_argument("--config", help="Path to custom settings.json configuration file")
267
+ parser.add_argument("--device-profile", help="Path to custom device_profile.json configuration file")
268
+ parser.add_argument("--env-file", help="Path to custom .env file")
269
+ parser.add_argument("--host", default="127.0.0.1", help="Supervisor host address (default: 127.0.0.1)")
270
+ parser.add_argument("--port", type=int, default=9333, help="Supervisor port (default: 9333)")
271
+
272
+ subparsers = parser.add_subparsers(dest="command", required=True)
273
+
274
+ # 1. Inspect Subcommand
275
+ p_inspect = subparsers.add_parser("inspect", help="Inspect URL and return full schema of inputs, dropdowns, buttons, uploads")
276
+ p_inspect.add_argument("--url", nargs="?", default=None, help="Target URL to inspect (optional if connecting to existing live session)")
277
+ p_inspect.add_argument("--cookies", help="Path to cookies JSON file")
278
+ p_inspect.add_argument("--output", help="Path to save full inspection JSON schema")
279
+ p_inspect.add_argument("--screenshot", help="Path to save visual screenshot")
280
+ p_inspect.add_argument("--unpack-options", action="store_true", help="Probe dropdowns to sample options (burns more tokens/time)")
281
+ p_inspect.add_argument("--headed", action="store_true", help="Run headed browser")
282
+ p_inspect.add_argument("--keep-alive", action="store_true", help="Keep browser open across sequential commands")
283
+ p_inspect.add_argument("--close-session", action="store_true", help="Terminate active live browser session and exit")
284
+ p_inspect.add_argument("--list-tabs", action="store_true", help="List all open tabs in active session")
285
+ p_inspect.add_argument("--tab", type=int, default=None, help="Target specific tab by index (0-based)")
286
+ p_inspect.add_argument("--timeout-minutes", type=int, default=30, help="Inactivity timeout in minutes (default: 30, 0 = disable)")
287
+ p_inspect.add_argument("--standalone", action="store_true", help="Run in-process via FlowOrchestrator instead of Master Supervisor")
288
+ p_inspect.add_argument("--host", default="127.0.0.1", help="Supervisor host address")
289
+ p_inspect.add_argument("--port", type=int, default=9333, help="Supervisor port")
290
+ p_inspect.add_argument("--config", help="Path to custom settings.json configuration file")
291
+ p_inspect.add_argument("--device-profile", help="Path to custom device_profile.json configuration file")
292
+ p_inspect.add_argument("--env-file", help="Path to custom .env file")
293
+
294
+ # 2. Apply / Fill Subcommand
295
+ p_apply = subparsers.add_parser("apply", help="Fill fields dynamically via JSON file or CLI flags, and optionally submit")
296
+ p_apply.add_argument("--url", nargs="?", default=None, help="Target URL to fill (optional if connecting to existing live session)")
297
+ p_apply.add_argument("--data", help="Path to JSON file containing key-value mappings")
298
+ p_apply.add_argument("--fill", action="append", help="Field to set, e.g. --fill 'First Name=John' (can be repeated)")
299
+ p_apply.add_argument("--press", action="append", help="Button to click, e.g. --press 'Next' (can be repeated)")
300
+ p_apply.add_argument("--upload", action="append", help="File to upload, e.g. --upload 'resume=sample.pdf' (can be repeated)")
301
+ p_apply.add_argument("--cookies", help="Path to cookies JSON file")
302
+ p_apply.add_argument("--submit", action="store_true", help="Click Apply/Submit after filling")
303
+ p_apply.add_argument("--screenshot", help="Path to save screenshot")
304
+ p_apply.add_argument("--headed", action="store_true", help="Run headed browser")
305
+ p_apply.add_argument("--keep-alive", action="store_true", help="Keep browser open across sequential commands")
306
+ p_apply.add_argument("--close-session", action="store_true", help="Terminate active live browser session and exit")
307
+ p_apply.add_argument("--list-tabs", action="store_true", help="List all open tabs in active session")
308
+ p_apply.add_argument("--tab", type=int, default=None, help="Target specific tab by index (0-based)")
309
+ p_apply.add_argument("--timeout-minutes", type=int, default=30, help="Inactivity timeout in minutes (default: 30, 0 = disable)")
310
+ p_apply.add_argument("--no-auto-inspect", action="store_true", help="Disable automatic post-interaction form inspection")
311
+ p_apply.add_argument("--standalone", action="store_true", help="Run in-process via FlowOrchestrator instead of Master Supervisor")
312
+ p_apply.add_argument("--host", default="127.0.0.1", help="Supervisor host address")
313
+ p_apply.add_argument("--port", type=int, default=9333, help="Supervisor port")
314
+ p_apply.add_argument("--config", help="Path to custom settings.json configuration file")
315
+ p_apply.add_argument("--device-profile", help="Path to custom device_profile.json configuration file")
316
+ p_apply.add_argument("--env-file", help="Path to custom .env file")
317
+
318
+ # 3. Interactive REPL Shell Subcommand
319
+ p_shell = subparsers.add_parser("shell", help="Launch interactive REPL automation shell")
320
+ p_shell.add_argument("--host", default="127.0.0.1", help="Supervisor host address")
321
+ p_shell.add_argument("--port", type=int, default=9333, help="Supervisor port")
322
+
323
+ # 4. Supervisor Service Management Subcommand
324
+ p_service = subparsers.add_parser("service", help="Manage Master Supervisor background daemon")
325
+ p_service.add_argument("service_action", choices=["status", "restart", "stop", "start"], help="Action to execute")
326
+ p_service.add_argument("--host", default="127.0.0.1", help="Supervisor host address")
327
+ p_service.add_argument("--port", type=int, default=9333, help="Supervisor port")
328
+
329
+ args = parser.parse_args()
330
+ if args.command == "inspect":
331
+ inspect_cmd(args)
332
+ elif args.command == "apply":
333
+ apply_cmd(args)
334
+ elif args.command == "shell":
335
+ shell_cmd(args)
336
+ elif args.command == "service":
337
+ service_cmd(args)
338
+
339
+
340
+ if __name__ == "__main__":
341
+ main()
config/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """Configuration package for Universal Web Applier."""
2
+
3
+ from config.settings import (
4
+ Settings,
5
+ get_settings,
6
+ load_device_profile,
7
+ reset_settings_cache,
8
+ PROJECT_ROOT,
9
+ CONFIG_DIR,
10
+ DEFAULT_SETTINGS_JSON,
11
+ DEFAULT_DEVICE_PROFILE_JSON,
12
+ )
13
+
14
+ __all__ = [
15
+ "Settings",
16
+ "get_settings",
17
+ "load_device_profile",
18
+ "reset_settings_cache",
19
+ "PROJECT_ROOT",
20
+ "CONFIG_DIR",
21
+ "DEFAULT_SETTINGS_JSON",
22
+ "DEFAULT_DEVICE_PROFILE_JSON",
23
+ ]