webtap-tool 0.1.4__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of webtap-tool might be problematic. Click here for more details.
- webtap/commands/setup.py +135 -4
- webtap/services/setup/__init__.py +178 -0
- webtap/services/setup/chrome.py +227 -0
- webtap/services/setup/desktop.py +228 -0
- webtap/services/setup/extension.py +101 -0
- webtap/services/setup/filters.py +89 -0
- webtap/services/setup/platform.py +126 -0
- {webtap_tool-0.1.4.dist-info → webtap_tool-0.2.0.dist-info}/METADATA +4 -1
- {webtap_tool-0.1.4.dist-info → webtap_tool-0.2.0.dist-info}/RECORD +11 -6
- webtap/services/setup.py +0 -229
- {webtap_tool-0.1.4.dist-info → webtap_tool-0.2.0.dist-info}/WHEEL +0 -0
- {webtap_tool-0.1.4.dist-info → webtap_tool-0.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Desktop/Application launcher setup (cross-platform)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
from .platform import get_platform_info
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# Desktop entry template for Linux
|
|
11
|
+
LINUX_DESKTOP_ENTRY = """[Desktop Entry]
|
|
12
|
+
Version=1.0
|
|
13
|
+
Type=Application
|
|
14
|
+
Name=Chrome Debug
|
|
15
|
+
GenericName=Web Browser (Debug Mode)
|
|
16
|
+
Comment=Chrome with remote debugging enabled
|
|
17
|
+
Icon=google-chrome
|
|
18
|
+
Categories=Development;WebBrowser;
|
|
19
|
+
MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/ftp;x-scheme-handler/http;x-scheme-handler/https;
|
|
20
|
+
StartupWMClass=Google-chrome
|
|
21
|
+
StartupNotify=true
|
|
22
|
+
Terminal=false
|
|
23
|
+
Exec={wrapper_path} %U
|
|
24
|
+
Actions=new-window;new-private-window;temp-profile;
|
|
25
|
+
|
|
26
|
+
[Desktop Action new-window]
|
|
27
|
+
Name=New Window
|
|
28
|
+
StartupWMClass=Google-chrome
|
|
29
|
+
Exec={wrapper_path}
|
|
30
|
+
|
|
31
|
+
[Desktop Action new-private-window]
|
|
32
|
+
Name=New Incognito Window
|
|
33
|
+
StartupWMClass=Google-chrome
|
|
34
|
+
Exec={wrapper_path} --incognito
|
|
35
|
+
|
|
36
|
+
[Desktop Action temp-profile]
|
|
37
|
+
Name=New Window (Temp Profile)
|
|
38
|
+
StartupWMClass=Google-chrome
|
|
39
|
+
Exec={wrapper_path} --temp
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Info.plist template for macOS app bundle
|
|
43
|
+
MACOS_INFO_PLIST = """<?xml version="1.0" encoding="UTF-8"?>
|
|
44
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
45
|
+
<plist version="1.0">
|
|
46
|
+
<dict>
|
|
47
|
+
<key>CFBundleExecutable</key>
|
|
48
|
+
<string>Chrome Debug</string>
|
|
49
|
+
<key>CFBundleIdentifier</key>
|
|
50
|
+
<string>com.webtap.chrome-debug</string>
|
|
51
|
+
<key>CFBundleName</key>
|
|
52
|
+
<string>Chrome Debug</string>
|
|
53
|
+
<key>CFBundleDisplayName</key>
|
|
54
|
+
<string>Chrome Debug</string>
|
|
55
|
+
<key>CFBundleVersion</key>
|
|
56
|
+
<string>1.0</string>
|
|
57
|
+
<key>CFBundleShortVersionString</key>
|
|
58
|
+
<string>1.0</string>
|
|
59
|
+
<key>CFBundlePackageType</key>
|
|
60
|
+
<string>APPL</string>
|
|
61
|
+
<key>CFBundleSignature</key>
|
|
62
|
+
<string>????</string>
|
|
63
|
+
<key>LSMinimumSystemVersion</key>
|
|
64
|
+
<string>10.12</string>
|
|
65
|
+
<key>CFBundleDocumentTypes</key>
|
|
66
|
+
<array>
|
|
67
|
+
<dict>
|
|
68
|
+
<key>CFBundleTypeName</key>
|
|
69
|
+
<string>HTML Document</string>
|
|
70
|
+
<key>CFBundleTypeRole</key>
|
|
71
|
+
<string>Viewer</string>
|
|
72
|
+
<key>LSItemContentTypes</key>
|
|
73
|
+
<array>
|
|
74
|
+
<string>public.html</string>
|
|
75
|
+
</array>
|
|
76
|
+
</dict>
|
|
77
|
+
<dict>
|
|
78
|
+
<key>CFBundleTypeName</key>
|
|
79
|
+
<string>Web Location</string>
|
|
80
|
+
<key>CFBundleTypeRole</key>
|
|
81
|
+
<string>Viewer</string>
|
|
82
|
+
<key>LSItemContentTypes</key>
|
|
83
|
+
<array>
|
|
84
|
+
<string>public.url</string>
|
|
85
|
+
</array>
|
|
86
|
+
</dict>
|
|
87
|
+
</array>
|
|
88
|
+
<key>CFBundleURLTypes</key>
|
|
89
|
+
<array>
|
|
90
|
+
<dict>
|
|
91
|
+
<key>CFBundleURLName</key>
|
|
92
|
+
<string>Web site URL</string>
|
|
93
|
+
<key>CFBundleURLSchemes</key>
|
|
94
|
+
<array>
|
|
95
|
+
<string>http</string>
|
|
96
|
+
<string>https</string>
|
|
97
|
+
</array>
|
|
98
|
+
</dict>
|
|
99
|
+
</array>
|
|
100
|
+
</dict>
|
|
101
|
+
</plist>"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class DesktopSetupService:
|
|
105
|
+
"""Platform-appropriate GUI launcher setup."""
|
|
106
|
+
|
|
107
|
+
def __init__(self):
|
|
108
|
+
self.info = get_platform_info()
|
|
109
|
+
self.paths = self.info["paths"]
|
|
110
|
+
self.chrome = self.info["chrome"]
|
|
111
|
+
|
|
112
|
+
# Unified wrapper path: ~/.local/bin/chrome-debug
|
|
113
|
+
self.wrapper_name = self.chrome["wrapper_name"] # chrome-debug
|
|
114
|
+
self.wrapper_path = self.paths["bin_dir"] / self.wrapper_name
|
|
115
|
+
|
|
116
|
+
def install_launcher(self, force: bool = False) -> Dict[str, Any]:
|
|
117
|
+
"""Install platform-appropriate launcher.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
force: Overwrite existing launcher
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Installation result
|
|
124
|
+
"""
|
|
125
|
+
# Check if wrapper exists first
|
|
126
|
+
if not self.wrapper_path.exists():
|
|
127
|
+
return {
|
|
128
|
+
"success": False,
|
|
129
|
+
"message": "Chrome wrapper 'chrome-debug' not found. Run 'setup-chrome' first",
|
|
130
|
+
"path": None,
|
|
131
|
+
"details": f"Expected wrapper at {self.wrapper_path}",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if self.info["is_macos"]:
|
|
135
|
+
return self._install_macos_app(force)
|
|
136
|
+
else:
|
|
137
|
+
return self._install_linux_desktop(force)
|
|
138
|
+
|
|
139
|
+
def _install_macos_app(self, force: bool) -> Dict[str, Any]:
|
|
140
|
+
"""Create .app bundle for macOS.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
force: Overwrite existing app
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Installation result
|
|
147
|
+
"""
|
|
148
|
+
app_path = self.paths["applications_dir"] / "Chrome Debug.app"
|
|
149
|
+
|
|
150
|
+
if app_path.exists() and not force:
|
|
151
|
+
return {
|
|
152
|
+
"success": False,
|
|
153
|
+
"message": "Chrome Debug app already exists",
|
|
154
|
+
"path": str(app_path),
|
|
155
|
+
"details": "Use --force to overwrite",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Create app structure
|
|
159
|
+
contents_dir = app_path / "Contents"
|
|
160
|
+
macos_dir = contents_dir / "MacOS"
|
|
161
|
+
macos_dir.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
# Create launcher script
|
|
164
|
+
launcher_path = macos_dir / "Chrome Debug"
|
|
165
|
+
launcher_content = f"""#!/bin/bash
|
|
166
|
+
# Chrome Debug app launcher
|
|
167
|
+
# Uses absolute path to wrapper
|
|
168
|
+
exec "{self.wrapper_path.expanduser()}" "$@"
|
|
169
|
+
"""
|
|
170
|
+
launcher_path.write_text(launcher_content)
|
|
171
|
+
launcher_path.chmod(0o755)
|
|
172
|
+
|
|
173
|
+
# Create Info.plist
|
|
174
|
+
plist_path = contents_dir / "Info.plist"
|
|
175
|
+
plist_path.write_text(MACOS_INFO_PLIST)
|
|
176
|
+
|
|
177
|
+
logger.info(f"Created Chrome Debug app at {app_path}")
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
"success": True,
|
|
181
|
+
"message": "Chrome Debug app created successfully",
|
|
182
|
+
"path": str(app_path),
|
|
183
|
+
"details": "Available in Launchpad and Spotlight search",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def _install_linux_desktop(self, force: bool) -> Dict[str, Any]:
|
|
187
|
+
"""Install .desktop file for Linux.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
force: Overwrite existing desktop entry
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Installation result
|
|
194
|
+
"""
|
|
195
|
+
# Create separate Chrome Debug desktop entry (doesn't override system Chrome)
|
|
196
|
+
desktop_path = self.paths["applications_dir"] / "chrome-debug.desktop"
|
|
197
|
+
|
|
198
|
+
if desktop_path.exists() and not force:
|
|
199
|
+
return {
|
|
200
|
+
"success": False,
|
|
201
|
+
"message": "Desktop entry already exists",
|
|
202
|
+
"path": str(desktop_path),
|
|
203
|
+
"details": "Use --force to overwrite",
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Create Chrome Debug desktop entry
|
|
207
|
+
desktop_content = self._create_chrome_debug_desktop()
|
|
208
|
+
|
|
209
|
+
# Create directory and save
|
|
210
|
+
desktop_path.parent.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
desktop_path.write_text(desktop_content)
|
|
212
|
+
desktop_path.chmod(0o644) # Standard permissions for desktop files
|
|
213
|
+
|
|
214
|
+
logger.info(f"Installed desktop entry to {desktop_path}")
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
"success": True,
|
|
218
|
+
"message": "Installed Chrome Debug desktop entry",
|
|
219
|
+
"path": str(desktop_path),
|
|
220
|
+
"details": "Available in application menu as 'Chrome Debug'",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
def _create_chrome_debug_desktop(self) -> str:
|
|
224
|
+
"""Create Chrome Debug desktop entry with absolute paths."""
|
|
225
|
+
# Use absolute expanded path for Exec lines
|
|
226
|
+
wrapper_abs_path = self.wrapper_path.expanduser()
|
|
227
|
+
|
|
228
|
+
return LINUX_DESKTOP_ENTRY.format(wrapper_path=wrapper_abs_path)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Chrome extension setup service (cross-platform)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from .platform import get_platform_info, ensure_directories
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# GitHub URLs for extension files
|
|
14
|
+
EXTENSION_BASE_URL = "https://raw.githubusercontent.com/angelsen/tap-tools/main/packages/webtap/extension"
|
|
15
|
+
EXTENSION_FILES = ["manifest.json", "popup.html", "popup.js"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExtensionSetupService:
|
|
19
|
+
"""Chrome extension installation service."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.info = get_platform_info()
|
|
23
|
+
self.paths = self.info["paths"]
|
|
24
|
+
|
|
25
|
+
# Extension goes in data directory (persistent app data)
|
|
26
|
+
self.extension_dir = self.paths["data_dir"] / "extension"
|
|
27
|
+
|
|
28
|
+
def install_extension(self, force: bool = False) -> Dict[str, Any]:
|
|
29
|
+
"""Install Chrome extension to platform-appropriate location.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
force: Overwrite existing files
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Installation result
|
|
36
|
+
"""
|
|
37
|
+
# Check if exists (manifest.json is required file)
|
|
38
|
+
if (self.extension_dir / "manifest.json").exists() and not force:
|
|
39
|
+
return {
|
|
40
|
+
"success": False,
|
|
41
|
+
"message": f"Extension already exists at {self.extension_dir}",
|
|
42
|
+
"path": str(self.extension_dir),
|
|
43
|
+
"details": "Use --force to overwrite",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Ensure base directories exist
|
|
47
|
+
ensure_directories()
|
|
48
|
+
|
|
49
|
+
# Create extension directory
|
|
50
|
+
self.extension_dir.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
# Download each file
|
|
53
|
+
downloaded = []
|
|
54
|
+
failed = []
|
|
55
|
+
|
|
56
|
+
for filename in EXTENSION_FILES:
|
|
57
|
+
url = f"{EXTENSION_BASE_URL}/{filename}"
|
|
58
|
+
target_file = self.extension_dir / filename
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
logger.info(f"Downloading {filename}")
|
|
62
|
+
response = requests.get(url, timeout=10)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
|
|
65
|
+
# For manifest.json, validate it's proper JSON
|
|
66
|
+
if filename == "manifest.json":
|
|
67
|
+
json.loads(response.text)
|
|
68
|
+
|
|
69
|
+
target_file.write_text(response.text)
|
|
70
|
+
downloaded.append(filename)
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Failed to download {filename}: {e}")
|
|
74
|
+
failed.append(filename)
|
|
75
|
+
|
|
76
|
+
# Determine success level
|
|
77
|
+
if not downloaded:
|
|
78
|
+
return {
|
|
79
|
+
"success": False,
|
|
80
|
+
"message": "Failed to download any extension files",
|
|
81
|
+
"path": None,
|
|
82
|
+
"details": "Check network connection and try again",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if failed:
|
|
86
|
+
# Partial success - some files downloaded
|
|
87
|
+
return {
|
|
88
|
+
"success": True, # Partial is still success
|
|
89
|
+
"message": f"Downloaded {len(downloaded)}/{len(EXTENSION_FILES)} files",
|
|
90
|
+
"path": str(self.extension_dir),
|
|
91
|
+
"details": f"Failed: {', '.join(failed)}",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logger.info(f"Extension installed to {self.extension_dir}")
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"success": True,
|
|
98
|
+
"message": "Downloaded Chrome extension",
|
|
99
|
+
"path": str(self.extension_dir),
|
|
100
|
+
"details": f"Files: {', '.join(downloaded)}",
|
|
101
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Filter setup service (cross-platform)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .platform import get_platform_info
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# GitHub URL for filters
|
|
15
|
+
FILTERS_URL = "https://raw.githubusercontent.com/angelsen/tap-tools/main/packages/webtap/data/filters.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FilterSetupService:
|
|
19
|
+
"""Filter configuration installation service."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.info = get_platform_info()
|
|
23
|
+
self.paths = self.info["paths"]
|
|
24
|
+
|
|
25
|
+
# Filters go in the current working directory's .webtap folder
|
|
26
|
+
# (project-specific, not global)
|
|
27
|
+
self.filters_path = Path.cwd() / ".webtap" / "filters.json"
|
|
28
|
+
|
|
29
|
+
def install_filters(self, force: bool = False) -> Dict[str, Any]:
|
|
30
|
+
"""Install filters to .webtap/filters.json in current directory.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
force: Overwrite existing file
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Installation result
|
|
37
|
+
"""
|
|
38
|
+
# Check if exists
|
|
39
|
+
if self.filters_path.exists() and not force:
|
|
40
|
+
return {
|
|
41
|
+
"success": False,
|
|
42
|
+
"message": f"Filters already exist at {self.filters_path}",
|
|
43
|
+
"path": str(self.filters_path),
|
|
44
|
+
"details": "Use --force to overwrite",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Download from GitHub
|
|
48
|
+
try:
|
|
49
|
+
logger.info(f"Downloading filters from {FILTERS_URL}")
|
|
50
|
+
response = requests.get(FILTERS_URL, timeout=10)
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
|
|
53
|
+
# Validate it's proper JSON
|
|
54
|
+
filters_data = json.loads(response.text)
|
|
55
|
+
|
|
56
|
+
# Quick validation - should have dict structure
|
|
57
|
+
if not isinstance(filters_data, dict):
|
|
58
|
+
return {
|
|
59
|
+
"success": False,
|
|
60
|
+
"message": "Invalid filter format - expected JSON object",
|
|
61
|
+
"path": None,
|
|
62
|
+
"details": None,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Count categories for user feedback
|
|
66
|
+
category_count = len(filters_data)
|
|
67
|
+
|
|
68
|
+
# Create directory and save
|
|
69
|
+
self.filters_path.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
self.filters_path.write_text(response.text)
|
|
71
|
+
|
|
72
|
+
logger.info(f"Saved {category_count} filter categories to {self.filters_path}")
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"success": True,
|
|
76
|
+
"message": f"Downloaded {category_count} filter categories",
|
|
77
|
+
"path": str(self.filters_path),
|
|
78
|
+
"details": f"Categories: {', '.join(filters_data.keys())}",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
except requests.RequestException as e:
|
|
82
|
+
logger.error(f"Network error downloading filters: {e}")
|
|
83
|
+
return {"success": False, "message": f"Network error: {e}", "path": None, "details": None}
|
|
84
|
+
except json.JSONDecodeError as e:
|
|
85
|
+
logger.error(f"Invalid JSON in filters: {e}")
|
|
86
|
+
return {"success": False, "message": f"Invalid JSON format: {e}", "path": None, "details": None}
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"Unexpected error: {e}")
|
|
89
|
+
return {"success": False, "message": f"Failed to download filters: {e}", "path": None, "details": None}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Platform detection and path management using platformdirs."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import platformdirs
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_platform_paths() -> dict[str, Path]:
|
|
12
|
+
"""Get platform-appropriate paths using platformdirs.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Dictionary of paths for config, data, cache, runtime, and state directories.
|
|
16
|
+
"""
|
|
17
|
+
app_name = "webtap"
|
|
18
|
+
app_author = "webtap"
|
|
19
|
+
|
|
20
|
+
dirs = platformdirs.PlatformDirs(app_name, app_author)
|
|
21
|
+
|
|
22
|
+
paths = {
|
|
23
|
+
"config_dir": Path(dirs.user_config_dir), # ~/.config/webtap or ~/Library/Application Support/webtap
|
|
24
|
+
"data_dir": Path(dirs.user_data_dir), # ~/.local/share/webtap or ~/Library/Application Support/webtap
|
|
25
|
+
"cache_dir": Path(dirs.user_cache_dir), # ~/.cache/webtap or ~/Library/Caches/webtap
|
|
26
|
+
"state_dir": Path(dirs.user_state_dir), # ~/.local/state/webtap or ~/Library/Application Support/webtap
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Runtime dir (not available on all platforms)
|
|
30
|
+
try:
|
|
31
|
+
paths["runtime_dir"] = Path(dirs.user_runtime_dir)
|
|
32
|
+
except AttributeError:
|
|
33
|
+
# Fallback for platforms without runtime dir
|
|
34
|
+
paths["runtime_dir"] = Path("/tmp") / app_name
|
|
35
|
+
|
|
36
|
+
return paths
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_chrome_path() -> Optional[Path]:
|
|
40
|
+
"""Find Chrome executable path for current platform.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Path to Chrome executable or None if not found.
|
|
44
|
+
"""
|
|
45
|
+
system = platform.system()
|
|
46
|
+
|
|
47
|
+
if system == "Darwin":
|
|
48
|
+
# macOS standard locations
|
|
49
|
+
candidates = [
|
|
50
|
+
Path("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
|
|
51
|
+
Path.home() / "Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
52
|
+
]
|
|
53
|
+
elif system == "Linux":
|
|
54
|
+
# Linux standard locations
|
|
55
|
+
candidates = [
|
|
56
|
+
Path("/usr/bin/google-chrome"),
|
|
57
|
+
Path("/usr/bin/google-chrome-stable"),
|
|
58
|
+
Path("/usr/bin/chromium"),
|
|
59
|
+
Path("/usr/bin/chromium-browser"),
|
|
60
|
+
Path("/snap/bin/chromium"),
|
|
61
|
+
]
|
|
62
|
+
else:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
for path in candidates:
|
|
66
|
+
if path.exists():
|
|
67
|
+
return path
|
|
68
|
+
|
|
69
|
+
# Try to find in PATH
|
|
70
|
+
for name in ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]:
|
|
71
|
+
if found := shutil.which(name):
|
|
72
|
+
return Path(found)
|
|
73
|
+
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_platform_info() -> dict:
|
|
78
|
+
"""Get comprehensive platform information.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dictionary with system info, paths, and capabilities.
|
|
82
|
+
"""
|
|
83
|
+
system = platform.system()
|
|
84
|
+
paths = get_platform_paths()
|
|
85
|
+
|
|
86
|
+
# Unified paths for both platforms
|
|
87
|
+
paths["bin_dir"] = Path.home() / ".local/bin" # User space, no sudo needed
|
|
88
|
+
wrapper_name = "chrome-debug" # Same name on both platforms
|
|
89
|
+
|
|
90
|
+
# Platform-specific launcher locations
|
|
91
|
+
if system == "Darwin":
|
|
92
|
+
paths["applications_dir"] = Path.home() / "Applications"
|
|
93
|
+
else: # Linux
|
|
94
|
+
paths["applications_dir"] = Path.home() / ".local/share/applications"
|
|
95
|
+
|
|
96
|
+
chrome_path = get_chrome_path()
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"system": system.lower(),
|
|
100
|
+
"is_macos": system == "Darwin",
|
|
101
|
+
"is_linux": system == "Linux",
|
|
102
|
+
"paths": paths,
|
|
103
|
+
"chrome": {
|
|
104
|
+
"path": chrome_path,
|
|
105
|
+
"found": chrome_path is not None,
|
|
106
|
+
"wrapper_name": wrapper_name,
|
|
107
|
+
},
|
|
108
|
+
"capabilities": {
|
|
109
|
+
"desktop_files": system == "Linux",
|
|
110
|
+
"app_bundles": system == "Darwin",
|
|
111
|
+
"bindfs": system == "Linux" and shutil.which("bindfs") is not None,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def ensure_directories() -> None:
|
|
117
|
+
"""Ensure all required directories exist with proper permissions."""
|
|
118
|
+
paths = get_platform_paths()
|
|
119
|
+
|
|
120
|
+
for name, path in paths.items():
|
|
121
|
+
if name != "runtime_dir": # Runtime dir is often system-managed
|
|
122
|
+
path.mkdir(parents=True, exist_ok=True, mode=0o755)
|
|
123
|
+
|
|
124
|
+
# Ensure bin directory exists
|
|
125
|
+
info = get_platform_info()
|
|
126
|
+
info["paths"]["bin_dir"].mkdir(parents=True, exist_ok=True, mode=0o755)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webtap-tool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Terminal-based web page inspector for AI debugging sessions
|
|
5
5
|
Author-email: Fredrik Angelsen <fredrikangelsen@gmail.com>
|
|
6
6
|
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
8
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
7
9
|
Classifier: Programming Language :: Python :: 3.12
|
|
8
10
|
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
9
11
|
Classifier: Topic :: Software Development :: Debuggers
|
|
@@ -15,6 +17,7 @@ Requires-Dist: fastapi>=0.116.1
|
|
|
15
17
|
Requires-Dist: httpx>=0.28.1
|
|
16
18
|
Requires-Dist: lxml>=6.0.1
|
|
17
19
|
Requires-Dist: msgpack-python>=0.5.6
|
|
20
|
+
Requires-Dist: platformdirs>=4.4.0
|
|
18
21
|
Requires-Dist: protobuf>=6.32.0
|
|
19
22
|
Requires-Dist: pyjwt>=2.10.1
|
|
20
23
|
Requires-Dist: pyyaml>=6.0.2
|
|
@@ -28,7 +28,7 @@ webtap/commands/javascript.py,sha256=QpQdqqoQwwTyz1lpibZ92XKOL89scu_ndgSjkhaYuDk
|
|
|
28
28
|
webtap/commands/launch.py,sha256=iZDLundKlxKRLKf3Vz5at42-tp2f-Uj5wZf7fbhBfA0,2202
|
|
29
29
|
webtap/commands/navigation.py,sha256=Mapawp2AZTJQaws2uwlTgMUhqz7HlVTLxiZ06n_MQc0,6071
|
|
30
30
|
webtap/commands/network.py,sha256=hwZshGGdVsJ_9MFjOKJXT07I990JjZInw2LLnKXLQ5Y,2910
|
|
31
|
-
webtap/commands/setup.py,sha256=
|
|
31
|
+
webtap/commands/setup.py,sha256=B60hajR8tKWLJcjlhEL4zYClB5DWd57AqnwJry_cHrs,10098
|
|
32
32
|
webtap/services/README.md,sha256=rala_jtnNgSiQ1lFLM7x_UQ4SJZDceAm7dpkQMRTYaI,2346
|
|
33
33
|
webtap/services/__init__.py,sha256=IjFqu0Ak6D-r18aokcQMtenDV3fbelvfjTCejGv6CZ0,570
|
|
34
34
|
webtap/services/body.py,sha256=XQPa19y5eUc3XJ2TuwVK6kffO1VQoKqNs33MBBz7hzU,3913
|
|
@@ -36,8 +36,13 @@ webtap/services/console.py,sha256=XVfSKTvEHyyOdujsg85S3wtj1CdZhzKtWwlx25MvSv8,37
|
|
|
36
36
|
webtap/services/fetch.py,sha256=nl6bpU2Vnf40kau4-mqAnIkhC-7Lx2vbTJKUglz9KnE,13602
|
|
37
37
|
webtap/services/main.py,sha256=HcXdPuI7hzsxsNvfN0npGhj_M7HObc83Lr3fuy7BMeE,5673
|
|
38
38
|
webtap/services/network.py,sha256=0o_--F6YvmXqqFqrcjL1gc6Vr9V1Ytb_U7r_DSUWupA,3444
|
|
39
|
-
webtap/services/setup.py,sha256=
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
webtap/services/setup/__init__.py,sha256=rCi6HjyWQmtoBu6NwB1Aw3bEklxsC-bt1jPJ8rGeNgA,6635
|
|
40
|
+
webtap/services/setup/chrome.py,sha256=nNeb7j_yCSx5-Urw4RqbOE6x46gY_vR-s2-TCRMrc8U,7119
|
|
41
|
+
webtap/services/setup/desktop.py,sha256=pMH5gk14iGEZ7SyaBNb4pYt7C1xJSx47oFzQCBIB_E4,7317
|
|
42
|
+
webtap/services/setup/extension.py,sha256=OvTLuSi5u-kBAkqWAzfYt5lTNZrduXoCMZhFCuMisew,3318
|
|
43
|
+
webtap/services/setup/filters.py,sha256=lAPSLMH_KZQO-7bRkmURwzforx7C3SDrKEw2ZogN-Lo,3220
|
|
44
|
+
webtap/services/setup/platform.py,sha256=RQrhvp8mLg5Bssy5Slfl5SPVGo3BlUIn3lxVm-ZmkAM,3987
|
|
45
|
+
webtap_tool-0.2.0.dist-info/METADATA,sha256=e3ux3On__FO9C8B1LVQ12t30A-YtDhw-vHkbevZ7SlQ,17588
|
|
46
|
+
webtap_tool-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
47
|
+
webtap_tool-0.2.0.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
|
|
48
|
+
webtap_tool-0.2.0.dist-info/RECORD,,
|