webtap-tool 0.11.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 (64) hide show
  1. webtap/VISION.md +246 -0
  2. webtap/__init__.py +84 -0
  3. webtap/__main__.py +6 -0
  4. webtap/api/__init__.py +9 -0
  5. webtap/api/app.py +26 -0
  6. webtap/api/models.py +69 -0
  7. webtap/api/server.py +111 -0
  8. webtap/api/sse.py +182 -0
  9. webtap/api/state.py +89 -0
  10. webtap/app.py +79 -0
  11. webtap/cdp/README.md +275 -0
  12. webtap/cdp/__init__.py +12 -0
  13. webtap/cdp/har.py +302 -0
  14. webtap/cdp/schema/README.md +41 -0
  15. webtap/cdp/schema/cdp_protocol.json +32785 -0
  16. webtap/cdp/schema/cdp_version.json +8 -0
  17. webtap/cdp/session.py +667 -0
  18. webtap/client.py +81 -0
  19. webtap/commands/DEVELOPER_GUIDE.md +401 -0
  20. webtap/commands/TIPS.md +269 -0
  21. webtap/commands/__init__.py +29 -0
  22. webtap/commands/_builders.py +331 -0
  23. webtap/commands/_code_generation.py +110 -0
  24. webtap/commands/_tips.py +147 -0
  25. webtap/commands/_utils.py +273 -0
  26. webtap/commands/connection.py +220 -0
  27. webtap/commands/console.py +87 -0
  28. webtap/commands/fetch.py +310 -0
  29. webtap/commands/filters.py +116 -0
  30. webtap/commands/javascript.py +73 -0
  31. webtap/commands/js_export.py +73 -0
  32. webtap/commands/launch.py +72 -0
  33. webtap/commands/navigation.py +197 -0
  34. webtap/commands/network.py +136 -0
  35. webtap/commands/quicktype.py +306 -0
  36. webtap/commands/request.py +93 -0
  37. webtap/commands/selections.py +138 -0
  38. webtap/commands/setup.py +219 -0
  39. webtap/commands/to_model.py +163 -0
  40. webtap/daemon.py +185 -0
  41. webtap/daemon_state.py +53 -0
  42. webtap/filters.py +219 -0
  43. webtap/rpc/__init__.py +14 -0
  44. webtap/rpc/errors.py +49 -0
  45. webtap/rpc/framework.py +223 -0
  46. webtap/rpc/handlers.py +625 -0
  47. webtap/rpc/machine.py +84 -0
  48. webtap/services/README.md +83 -0
  49. webtap/services/__init__.py +15 -0
  50. webtap/services/console.py +124 -0
  51. webtap/services/dom.py +547 -0
  52. webtap/services/fetch.py +415 -0
  53. webtap/services/main.py +392 -0
  54. webtap/services/network.py +401 -0
  55. webtap/services/setup/__init__.py +185 -0
  56. webtap/services/setup/chrome.py +233 -0
  57. webtap/services/setup/desktop.py +255 -0
  58. webtap/services/setup/extension.py +147 -0
  59. webtap/services/setup/platform.py +162 -0
  60. webtap/services/state_snapshot.py +86 -0
  61. webtap_tool-0.11.0.dist-info/METADATA +535 -0
  62. webtap_tool-0.11.0.dist-info/RECORD +64 -0
  63. webtap_tool-0.11.0.dist-info/WHEEL +4 -0
  64. webtap_tool-0.11.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,162 @@
1
+ """Platform detection and path management using platformdirs.
2
+
3
+ PUBLIC API:
4
+ - get_platform_info: Get platform information dict with paths and Chrome location
5
+ - ensure_directories: Create required directories with proper permissions
6
+ - APP_NAME: Application name constant
7
+ """
8
+
9
+ import platform
10
+ import shutil
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ import platformdirs
15
+
16
+ # Application constants
17
+ APP_NAME = "webtap"
18
+ APP_AUTHOR = "webtap"
19
+
20
+ # Directory names
21
+ BIN_DIR_NAME = ".local/bin"
22
+ WRAPPER_NAME = "chrome-debug"
23
+ TMP_RUNTIME_DIR = "/tmp"
24
+
25
+ # Chrome executable names for Linux
26
+ CHROME_NAMES_LINUX = [
27
+ "google-chrome",
28
+ "google-chrome-stable",
29
+ "chromium",
30
+ "chromium-browser",
31
+ ]
32
+
33
+ # Chrome paths for macOS
34
+ CHROME_PATHS_MACOS = [
35
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
36
+ "Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # Relative to home
37
+ ]
38
+
39
+ # Chrome paths for Linux
40
+ CHROME_PATHS_LINUX = [
41
+ "/usr/bin/google-chrome",
42
+ "/usr/bin/google-chrome-stable",
43
+ "/usr/bin/chromium",
44
+ "/usr/bin/chromium-browser",
45
+ "/snap/bin/chromium",
46
+ ]
47
+
48
+ # Platform identifiers
49
+ PLATFORM_DARWIN = "Darwin"
50
+ PLATFORM_LINUX = "Linux"
51
+
52
+ # Application directories
53
+ MACOS_APPLICATIONS_DIR = "Applications"
54
+ LINUX_APPLICATIONS_DIR = ".local/share/applications"
55
+
56
+
57
+ def get_platform_paths() -> dict[str, Path]:
58
+ """Get platform-appropriate paths using platformdirs.
59
+
60
+ Returns:
61
+ Dictionary of paths for config, data, cache, runtime, and state directories.
62
+ """
63
+ dirs = platformdirs.PlatformDirs(APP_NAME, APP_AUTHOR)
64
+
65
+ paths = {
66
+ "config_dir": Path(dirs.user_config_dir), # ~/.config/webtap or ~/Library/Application Support/webtap
67
+ "data_dir": Path(dirs.user_data_dir), # ~/.local/share/webtap or ~/Library/Application Support/webtap
68
+ "cache_dir": Path(dirs.user_cache_dir), # ~/.cache/webtap or ~/Library/Caches/webtap
69
+ "state_dir": Path(dirs.user_state_dir), # ~/.local/state/webtap or ~/Library/Application Support/webtap
70
+ }
71
+
72
+ # Runtime dir (not available on all platforms)
73
+ try:
74
+ paths["runtime_dir"] = Path(dirs.user_runtime_dir)
75
+ except AttributeError:
76
+ # Fallback for platforms without runtime dir
77
+ paths["runtime_dir"] = Path(TMP_RUNTIME_DIR) / APP_NAME
78
+
79
+ return paths
80
+
81
+
82
+ def get_chrome_path() -> Optional[Path]:
83
+ """Find Chrome executable path for current platform.
84
+
85
+ Returns:
86
+ Path to Chrome executable or None if not found.
87
+ """
88
+ system = platform.system()
89
+
90
+ if system == PLATFORM_DARWIN:
91
+ # macOS standard locations
92
+ candidates = [
93
+ Path(CHROME_PATHS_MACOS[0]),
94
+ Path.home() / CHROME_PATHS_MACOS[1],
95
+ ]
96
+ elif system == PLATFORM_LINUX:
97
+ # Linux standard locations
98
+ candidates = [Path(p) for p in CHROME_PATHS_LINUX]
99
+ else:
100
+ return None
101
+
102
+ for path in candidates:
103
+ if path.exists():
104
+ return path
105
+
106
+ # Try to find in PATH
107
+ for name in CHROME_NAMES_LINUX:
108
+ if found := shutil.which(name):
109
+ return Path(found)
110
+
111
+ return None
112
+
113
+
114
+ def get_platform_info() -> dict:
115
+ """Get comprehensive platform information.
116
+
117
+ Returns:
118
+ Dictionary with system info, paths, and capabilities.
119
+ """
120
+ system = platform.system()
121
+ paths = get_platform_paths()
122
+
123
+ # Unified paths for both platforms
124
+ paths["bin_dir"] = Path.home() / BIN_DIR_NAME # User space, no sudo needed
125
+
126
+ # Platform-specific launcher locations
127
+ if system == PLATFORM_DARWIN:
128
+ paths["applications_dir"] = Path.home() / MACOS_APPLICATIONS_DIR
129
+ else: # Linux
130
+ paths["applications_dir"] = Path.home() / LINUX_APPLICATIONS_DIR
131
+
132
+ chrome_path = get_chrome_path()
133
+
134
+ return {
135
+ "system": system.lower(),
136
+ "is_macos": system == PLATFORM_DARWIN,
137
+ "is_linux": system == PLATFORM_LINUX,
138
+ "paths": paths,
139
+ "chrome": {
140
+ "path": chrome_path,
141
+ "found": chrome_path is not None,
142
+ "wrapper_name": WRAPPER_NAME,
143
+ },
144
+ "capabilities": {
145
+ "desktop_files": system == PLATFORM_LINUX,
146
+ "app_bundles": system == PLATFORM_DARWIN,
147
+ "bindfs": system == PLATFORM_LINUX and shutil.which("bindfs") is not None,
148
+ },
149
+ }
150
+
151
+
152
+ def ensure_directories() -> None:
153
+ """Ensure all required directories exist with proper permissions."""
154
+ paths = get_platform_paths()
155
+
156
+ for name, path in paths.items():
157
+ if name != "runtime_dir": # Runtime dir is often system-managed
158
+ path.mkdir(parents=True, exist_ok=True, mode=0o755)
159
+
160
+ # Ensure bin directory exists
161
+ info = get_platform_info()
162
+ info["paths"]["bin_dir"].mkdir(parents=True, exist_ok=True, mode=0o755)
@@ -0,0 +1,86 @@
1
+ """Immutable state snapshots for thread-safe SSE broadcasting."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class StateSnapshot:
9
+ """Immutable snapshot of WebTap state.
10
+
11
+ Frozen dataclass provides inherent thread safety - multiple threads can
12
+ read simultaneously without locks. Updated atomically when state changes.
13
+
14
+ Used by SSE broadcast to avoid lock contention between asyncio event loop
15
+ and background threads (WebSocket, disconnect handlers).
16
+
17
+ Attributes:
18
+ connected: Whether connected to Chrome page
19
+ page_id: Stable page identifier (empty if not connected)
20
+ page_title: Page title (empty if not connected)
21
+ page_url: Page URL (empty if not connected)
22
+ event_count: Total CDP events stored
23
+ fetch_enabled: Whether fetch interception is active
24
+ paused_count: Number of paused requests (if fetch enabled)
25
+ enabled_filters: Tuple of enabled filter category names
26
+ disabled_filters: Tuple of disabled filter category names
27
+ inspect_active: Whether element inspection mode is active
28
+ selections: Dict of selected elements (id -> element data)
29
+ prompt: Browser prompt text (unused, reserved)
30
+ pending_count: Number of pending element selections being processed
31
+ error_message: Current error message or None
32
+ error_timestamp: Error timestamp or None
33
+ """
34
+
35
+ # Connection state
36
+ connected: bool
37
+ page_id: str
38
+ page_title: str
39
+ page_url: str
40
+
41
+ # Event state
42
+ event_count: int
43
+
44
+ # Fetch interception state
45
+ fetch_enabled: bool
46
+ response_stage: bool
47
+ paused_count: int
48
+
49
+ # Filter state (immutable tuples)
50
+ enabled_filters: tuple[str, ...]
51
+ disabled_filters: tuple[str, ...]
52
+
53
+ # Browser/DOM state
54
+ inspect_active: bool
55
+ selections: dict[str, Any] # Dict is mutable but replaced atomically
56
+ prompt: str
57
+ pending_count: int
58
+
59
+ # Error state
60
+ error_message: str | None
61
+ error_timestamp: float | None
62
+
63
+ @classmethod
64
+ def create_empty(cls) -> "StateSnapshot":
65
+ """Create empty snapshot for disconnected state."""
66
+ return cls(
67
+ connected=False,
68
+ page_id="",
69
+ page_title="",
70
+ page_url="",
71
+ event_count=0,
72
+ fetch_enabled=False,
73
+ response_stage=False,
74
+ paused_count=0,
75
+ enabled_filters=(),
76
+ disabled_filters=(),
77
+ inspect_active=False,
78
+ selections={},
79
+ prompt="",
80
+ pending_count=0,
81
+ error_message=None,
82
+ error_timestamp=None,
83
+ )
84
+
85
+
86
+ __all__ = ["StateSnapshot"]