deepctl-cmd-debug-browser 0.1.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.
- deepctl_cmd_debug_browser-0.1.0/PKG-INFO +29 -0
- deepctl_cmd_debug_browser-0.1.0/README.md +3 -0
- deepctl_cmd_debug_browser-0.1.0/pyproject.toml +49 -0
- deepctl_cmd_debug_browser-0.1.0/setup.cfg +4 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser/__init__.py +11 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser/command.py +445 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser/models.py +72 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser/static/__init__.py +2 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser/static/debug.html +507 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/PKG-INFO +29 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/SOURCES.txt +13 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/dependency_links.txt +1 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/entry_points.txt +2 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/requires.txt +10 -0
- deepctl_cmd_debug_browser-0.1.0/src/deepctl_cmd_debug_browser.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-debug-browser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Browser debug subcommand for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,debug,browser,diagnostics
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: rich>=13.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: httpx>=0.24.0
|
|
22
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# deepctl-cmd-debug-browser
|
|
28
|
+
|
|
29
|
+
Browser debug subcommand for the Deepgram CLI (deepctl). This package provides the `browser` subcommand under the `debug` command group, allowing users to debug browser-related issues when using Deepgram's web-based features
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "deepctl-cmd-debug-browser"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Browser debug subcommand for deepctl"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
12
|
+
maintainers = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
keywords = ["deepgram", "cli", "debug", "browser", "diagnostics"]
|
|
22
|
+
requires-python = ">=3.10"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"deepctl-core>=0.1.0",
|
|
25
|
+
"click>=8.0.0",
|
|
26
|
+
"rich>=13.0.0",
|
|
27
|
+
"pydantic>=2.0.0",
|
|
28
|
+
"httpx>=0.24.0",
|
|
29
|
+
"aiohttp>=3.9.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=7.0.0", "pytest-asyncio>=0.21.0"]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
# Internal commands not meant to be called directly
|
|
37
|
+
|
|
38
|
+
[project.entry-points."deepctl.subcommands.debug"]
|
|
39
|
+
browser = "deepctl_cmd_debug_browser.command:BrowserCommand"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools]
|
|
42
|
+
package-dir = { "" = "src" }
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
include = ["deepctl_cmd_debug_browser*"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
deepctl_cmd_debug_browser = ["static/*.html"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Browser debug subcommand for deepctl."""
|
|
2
|
+
|
|
3
|
+
from .command import BrowserCommand
|
|
4
|
+
from .models import BrowserCapabilities, BrowserCapability, BrowserDebugResult
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"BrowserCapabilities",
|
|
8
|
+
"BrowserCapability",
|
|
9
|
+
"BrowserCommand",
|
|
10
|
+
"BrowserDebugResult",
|
|
11
|
+
]
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
"""Browser debug command for deepctl."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib.resources
|
|
5
|
+
import json
|
|
6
|
+
import socket
|
|
7
|
+
import time
|
|
8
|
+
import webbrowser
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from aiohttp import WSMsgType, web
|
|
13
|
+
from deepctl_core import AuthManager, BaseCommand, Config, DeepgramClient
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from .models import (
|
|
20
|
+
BrowserCapabilities,
|
|
21
|
+
BrowserCapability,
|
|
22
|
+
BrowserDebugResult,
|
|
23
|
+
MessageType,
|
|
24
|
+
WebSocketMessage,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BrowserCommand(BaseCommand):
|
|
31
|
+
"""Debug browser-related connectivity and access issues."""
|
|
32
|
+
|
|
33
|
+
name = "browser"
|
|
34
|
+
help = "Debug browser compatibility and capabilities for Deepgram services"
|
|
35
|
+
short_help = "Debug browser capabilities"
|
|
36
|
+
|
|
37
|
+
# Browser debug doesn't require auth
|
|
38
|
+
requires_auth = False
|
|
39
|
+
requires_project = False
|
|
40
|
+
ci_friendly = False # This command opens a browser
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.messages: list[WebSocketMessage] = []
|
|
45
|
+
self.capabilities_data: dict[str, Any] = {}
|
|
46
|
+
self.ws_clients: set[Any] = set()
|
|
47
|
+
self.debug_complete = False
|
|
48
|
+
self.start_time: float | None = None
|
|
49
|
+
|
|
50
|
+
def get_arguments(self) -> list[dict[str, Any]]:
|
|
51
|
+
"""Get command arguments and options."""
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
"names": ["--port", "-p"],
|
|
55
|
+
"help": (
|
|
56
|
+
"Port to run the debug server on "
|
|
57
|
+
"(default: auto-select starting from 3000)"
|
|
58
|
+
),
|
|
59
|
+
"type": int,
|
|
60
|
+
"is_option": True,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"names": ["--no-browser"],
|
|
64
|
+
"help": "Don't automatically open the browser",
|
|
65
|
+
"is_flag": True,
|
|
66
|
+
"is_option": True,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"names": ["--timeout"],
|
|
70
|
+
"help": (
|
|
71
|
+
"Timeout in seconds to wait for browser connection "
|
|
72
|
+
"(default: 60)"
|
|
73
|
+
),
|
|
74
|
+
"type": int,
|
|
75
|
+
"default": 60,
|
|
76
|
+
"is_option": True,
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
def find_available_port(self, start_port: int = 3000) -> int:
|
|
81
|
+
"""Find an available port starting from the given port."""
|
|
82
|
+
for port in range(start_port, start_port + 100):
|
|
83
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
84
|
+
try:
|
|
85
|
+
sock.bind(("localhost", port))
|
|
86
|
+
sock.close()
|
|
87
|
+
return port
|
|
88
|
+
except OSError:
|
|
89
|
+
continue
|
|
90
|
+
raise RuntimeError(
|
|
91
|
+
f"Could not find available port starting from {start_port}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def websocket_handler(self, request: Any) -> Any:
|
|
95
|
+
"""Handle WebSocket connections from the browser."""
|
|
96
|
+
ws = web.WebSocketResponse()
|
|
97
|
+
await ws.prepare(request)
|
|
98
|
+
self.ws_clients.add(ws)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
async for msg in ws:
|
|
102
|
+
if msg.type == WSMsgType.TEXT:
|
|
103
|
+
data = json.loads(msg.data)
|
|
104
|
+
ws_msg = WebSocketMessage(
|
|
105
|
+
type=MessageType(data.get("type", "info")),
|
|
106
|
+
data=data.get("data", {}),
|
|
107
|
+
message=data.get("message"),
|
|
108
|
+
)
|
|
109
|
+
self.messages.append(ws_msg)
|
|
110
|
+
|
|
111
|
+
# Handle different message types
|
|
112
|
+
if ws_msg.type == MessageType.CAPABILITY_CHECK:
|
|
113
|
+
cap_data = ws_msg.data.get("result", {})
|
|
114
|
+
cap_key = ws_msg.data.get("capability")
|
|
115
|
+
if cap_key and cap_data:
|
|
116
|
+
self.capabilities_data[cap_key] = (
|
|
117
|
+
BrowserCapability(**cap_data)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
elif ws_msg.type == MessageType.COMPLETE:
|
|
121
|
+
# Store complete capabilities
|
|
122
|
+
if "capabilities" in ws_msg.data:
|
|
123
|
+
caps = ws_msg.data["capabilities"]
|
|
124
|
+
# Convert all capability data
|
|
125
|
+
cap_dict = {}
|
|
126
|
+
for key in [
|
|
127
|
+
"web_audio_api",
|
|
128
|
+
"audio_context",
|
|
129
|
+
"audio_worklet",
|
|
130
|
+
"websocket_api",
|
|
131
|
+
"fetch_api",
|
|
132
|
+
"es6_features",
|
|
133
|
+
"dom_apis",
|
|
134
|
+
"console_api",
|
|
135
|
+
"timer_apis",
|
|
136
|
+
"secure_context",
|
|
137
|
+
]:
|
|
138
|
+
if key in caps:
|
|
139
|
+
cap_dict[key] = BrowserCapability(
|
|
140
|
+
**caps[key]
|
|
141
|
+
)
|
|
142
|
+
elif key in self.capabilities_data:
|
|
143
|
+
cap_dict[key] = self.capabilities_data[key]
|
|
144
|
+
|
|
145
|
+
# Store complete capabilities
|
|
146
|
+
self.capabilities_data = {
|
|
147
|
+
**cap_dict,
|
|
148
|
+
"user_agent": caps.get(
|
|
149
|
+
"user_agent", "Unknown"
|
|
150
|
+
),
|
|
151
|
+
"overall_compatible": caps.get(
|
|
152
|
+
"overall_compatible", False
|
|
153
|
+
),
|
|
154
|
+
}
|
|
155
|
+
self.debug_complete = True
|
|
156
|
+
elif msg.type == WSMsgType.ERROR:
|
|
157
|
+
console.print(
|
|
158
|
+
f"[red]WebSocket error: {ws.exception()}[/red]"
|
|
159
|
+
)
|
|
160
|
+
finally:
|
|
161
|
+
self.ws_clients.discard(ws)
|
|
162
|
+
await ws.close()
|
|
163
|
+
|
|
164
|
+
return ws
|
|
165
|
+
|
|
166
|
+
async def http_handler(self, request: Any) -> Any:
|
|
167
|
+
"""Serve the debug HTML page."""
|
|
168
|
+
if request.path == "/":
|
|
169
|
+
# Read the HTML template from the static directory
|
|
170
|
+
try:
|
|
171
|
+
files = importlib.resources.files("deepctl_cmd_debug_browser")
|
|
172
|
+
html_content = (files / "static" / "debug.html").read_text()
|
|
173
|
+
except Exception:
|
|
174
|
+
# Fallback to Path-based approach
|
|
175
|
+
html_path = Path(__file__).parent / "static" / "debug.html"
|
|
176
|
+
html_content = html_path.read_text()
|
|
177
|
+
|
|
178
|
+
return web.Response(text=html_content, content_type="text/html")
|
|
179
|
+
return web.Response(status=404)
|
|
180
|
+
|
|
181
|
+
async def run_servers(self, port: int, timeout: int) -> dict[str, Any]:
|
|
182
|
+
"""Run both HTTP and WebSocket servers."""
|
|
183
|
+
# Create aiohttp app
|
|
184
|
+
app = web.Application()
|
|
185
|
+
app.router.add_get("/", self.http_handler)
|
|
186
|
+
app.router.add_get("/ws", self.websocket_handler)
|
|
187
|
+
|
|
188
|
+
# Start HTTP server (which also handles WebSocket)
|
|
189
|
+
runner = web.AppRunner(app)
|
|
190
|
+
await runner.setup()
|
|
191
|
+
site = web.TCPSite(runner, "localhost", port)
|
|
192
|
+
await site.start()
|
|
193
|
+
|
|
194
|
+
console.print(
|
|
195
|
+
f"\n[green]✓[/green] Debug server started on port "
|
|
196
|
+
f"[cyan]{port}[/cyan]"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Wait for debug to complete or timeout
|
|
200
|
+
start_time = time.time()
|
|
201
|
+
while not self.debug_complete and (time.time() - start_time) < timeout:
|
|
202
|
+
await asyncio.sleep(0.1)
|
|
203
|
+
|
|
204
|
+
# Cleanup
|
|
205
|
+
await runner.cleanup()
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"completed": self.debug_complete,
|
|
209
|
+
"timed_out": not self.debug_complete
|
|
210
|
+
and (time.time() - start_time) >= timeout,
|
|
211
|
+
"duration": time.time() - start_time,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
def display_results(
|
|
215
|
+
self,
|
|
216
|
+
capabilities: BrowserCapabilities | None,
|
|
217
|
+
messages: list[WebSocketMessage],
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Display the debug results in a formatted way."""
|
|
220
|
+
console.print("\n[bold cyan]Browser Debug Results[/bold cyan]\n")
|
|
221
|
+
|
|
222
|
+
# Display messages/logs
|
|
223
|
+
if messages:
|
|
224
|
+
log_table = Table(
|
|
225
|
+
title="Debug Log", show_header=True, header_style="bold cyan"
|
|
226
|
+
)
|
|
227
|
+
log_table.add_column("Time", style="dim", width=12)
|
|
228
|
+
log_table.add_column("Type", width=10)
|
|
229
|
+
log_table.add_column("Message")
|
|
230
|
+
|
|
231
|
+
for msg in messages:
|
|
232
|
+
if msg.message:
|
|
233
|
+
time_str = msg.timestamp.strftime("%H:%M:%S.%f")[:-3]
|
|
234
|
+
type_style = {
|
|
235
|
+
MessageType.ERROR: "red",
|
|
236
|
+
MessageType.WARNING: "yellow",
|
|
237
|
+
MessageType.INFO: "cyan",
|
|
238
|
+
MessageType.CAPABILITY_CHECK: "green",
|
|
239
|
+
MessageType.COMPLETE: "magenta",
|
|
240
|
+
}.get(msg.type, "white")
|
|
241
|
+
|
|
242
|
+
log_table.add_row(
|
|
243
|
+
time_str,
|
|
244
|
+
f"[{type_style}]{msg.type.value}[/{type_style}]",
|
|
245
|
+
msg.message or "",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
console.print(log_table)
|
|
249
|
+
console.print()
|
|
250
|
+
|
|
251
|
+
# Display capabilities
|
|
252
|
+
if capabilities:
|
|
253
|
+
cap_table = Table(
|
|
254
|
+
title="Browser Capabilities",
|
|
255
|
+
show_header=True,
|
|
256
|
+
header_style="bold cyan",
|
|
257
|
+
)
|
|
258
|
+
cap_table.add_column("Feature", style="cyan", width=30)
|
|
259
|
+
cap_table.add_column("Status", width=15)
|
|
260
|
+
cap_table.add_column("Details", width=40)
|
|
261
|
+
|
|
262
|
+
# Add capability rows
|
|
263
|
+
capability_fields = [
|
|
264
|
+
("web_audio_api", "Web Audio API"),
|
|
265
|
+
("audio_context", "AudioContext"),
|
|
266
|
+
("audio_worklet", "AudioWorklet API"),
|
|
267
|
+
("websocket_api", "WebSocket API"),
|
|
268
|
+
("fetch_api", "Fetch API"),
|
|
269
|
+
("es6_features", "ES6+ Features"),
|
|
270
|
+
("dom_apis", "DOM APIs"),
|
|
271
|
+
("console_api", "Console API"),
|
|
272
|
+
("timer_apis", "Timer APIs"),
|
|
273
|
+
("secure_context", "Secure Context"),
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
for field, display_name in capability_fields:
|
|
277
|
+
cap = getattr(capabilities, field, None)
|
|
278
|
+
if cap:
|
|
279
|
+
status_icon = "✓" if cap.supported else "✗"
|
|
280
|
+
status_color = "green" if cap.supported else "red"
|
|
281
|
+
cap_table.add_row(
|
|
282
|
+
display_name,
|
|
283
|
+
f"[{status_color}]{status_icon} "
|
|
284
|
+
f"{cap.details}[/{status_color}]",
|
|
285
|
+
cap.version or "",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
console.print(cap_table)
|
|
289
|
+
|
|
290
|
+
# Overall compatibility
|
|
291
|
+
console.print(
|
|
292
|
+
f"\n[bold]User Agent:[/bold] {capabilities.user_agent}"
|
|
293
|
+
)
|
|
294
|
+
if capabilities.overall_compatible:
|
|
295
|
+
console.print(
|
|
296
|
+
"[bold green]✓ Browser is fully compatible with "
|
|
297
|
+
"Deepgram services[/bold green]"
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
console.print(
|
|
301
|
+
"[bold red]✗ Browser may have compatibility issues "
|
|
302
|
+
"with some Deepgram features[/bold red]"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def handle(
|
|
306
|
+
self,
|
|
307
|
+
config: Config,
|
|
308
|
+
auth_manager: AuthManager,
|
|
309
|
+
client: DeepgramClient,
|
|
310
|
+
**kwargs: Any,
|
|
311
|
+
) -> Any:
|
|
312
|
+
"""Handle browser debug command execution."""
|
|
313
|
+
port = kwargs.get("port")
|
|
314
|
+
no_browser = kwargs.get("no_browser", False)
|
|
315
|
+
timeout = kwargs.get("timeout", 60)
|
|
316
|
+
|
|
317
|
+
self.start_time = time.time()
|
|
318
|
+
|
|
319
|
+
# Find available port
|
|
320
|
+
if not port:
|
|
321
|
+
port = self.find_available_port()
|
|
322
|
+
|
|
323
|
+
# Show initial message
|
|
324
|
+
console.print(
|
|
325
|
+
Panel.fit(
|
|
326
|
+
"[cyan]🌐 Browser Debug[/cyan]\n\n"
|
|
327
|
+
"Starting debug server to check browser capabilities...",
|
|
328
|
+
title="Deepctl Browser Debug",
|
|
329
|
+
border_style="cyan",
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
url = f"http://localhost:{port}"
|
|
334
|
+
|
|
335
|
+
# Prompt user to open browser
|
|
336
|
+
if not no_browser:
|
|
337
|
+
console.print(
|
|
338
|
+
"\n[yellow]Press Enter to open the debugger in your "
|
|
339
|
+
"browser...[/yellow]"
|
|
340
|
+
)
|
|
341
|
+
input()
|
|
342
|
+
webbrowser.open(url)
|
|
343
|
+
browser_opened = True
|
|
344
|
+
else:
|
|
345
|
+
console.print(
|
|
346
|
+
f"\n[dim]Open [cyan]{url}[/cyan] in your browser to start "
|
|
347
|
+
f"debugging[/dim]"
|
|
348
|
+
)
|
|
349
|
+
browser_opened = False
|
|
350
|
+
|
|
351
|
+
# Run the async event loop
|
|
352
|
+
with Progress(
|
|
353
|
+
SpinnerColumn(),
|
|
354
|
+
TextColumn("[progress.description]{task.description}"),
|
|
355
|
+
console=console,
|
|
356
|
+
transient=True,
|
|
357
|
+
) as progress:
|
|
358
|
+
task = progress.add_task(
|
|
359
|
+
"Waiting for browser connection...", total=None
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Run servers
|
|
363
|
+
loop = asyncio.new_event_loop()
|
|
364
|
+
asyncio.set_event_loop(loop)
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
result = loop.run_until_complete(
|
|
368
|
+
self.run_servers(port, timeout)
|
|
369
|
+
)
|
|
370
|
+
finally:
|
|
371
|
+
loop.close()
|
|
372
|
+
|
|
373
|
+
progress.update(task, completed=True)
|
|
374
|
+
|
|
375
|
+
# Process results
|
|
376
|
+
capabilities = None
|
|
377
|
+
if (
|
|
378
|
+
self.capabilities_data
|
|
379
|
+
and "overall_compatible" in self.capabilities_data
|
|
380
|
+
):
|
|
381
|
+
# Build BrowserCapabilities object
|
|
382
|
+
cap_dict = {}
|
|
383
|
+
for key in [
|
|
384
|
+
"web_audio_api",
|
|
385
|
+
"audio_context",
|
|
386
|
+
"audio_worklet",
|
|
387
|
+
"websocket_api",
|
|
388
|
+
"fetch_api",
|
|
389
|
+
"es6_features",
|
|
390
|
+
"dom_apis",
|
|
391
|
+
"console_api",
|
|
392
|
+
"timer_apis",
|
|
393
|
+
"secure_context",
|
|
394
|
+
]:
|
|
395
|
+
if key in self.capabilities_data:
|
|
396
|
+
cap_dict[key] = self.capabilities_data[key]
|
|
397
|
+
|
|
398
|
+
if all(key in cap_dict for key in cap_dict):
|
|
399
|
+
capabilities = BrowserCapabilities(
|
|
400
|
+
**cap_dict,
|
|
401
|
+
user_agent=self.capabilities_data.get(
|
|
402
|
+
"user_agent", "Unknown"
|
|
403
|
+
),
|
|
404
|
+
overall_compatible=self.capabilities_data.get(
|
|
405
|
+
"overall_compatible", False
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# Display results
|
|
410
|
+
if result["completed"]:
|
|
411
|
+
console.print(
|
|
412
|
+
"\n[green]✓ Debug session completed successfully![/green]"
|
|
413
|
+
)
|
|
414
|
+
self.display_results(capabilities, self.messages)
|
|
415
|
+
elif result["timed_out"]:
|
|
416
|
+
console.print(
|
|
417
|
+
f"\n[red]✗ Debug session timed out after {timeout} "
|
|
418
|
+
f"seconds[/red]"
|
|
419
|
+
)
|
|
420
|
+
console.print("[dim]No browser connection was established.[/dim]")
|
|
421
|
+
else:
|
|
422
|
+
console.print("\n[yellow]Debug session ended[/yellow]")
|
|
423
|
+
|
|
424
|
+
# Build result
|
|
425
|
+
errors = [
|
|
426
|
+
msg.message
|
|
427
|
+
for msg in self.messages
|
|
428
|
+
if msg.type == MessageType.ERROR and msg.message
|
|
429
|
+
]
|
|
430
|
+
warnings = [
|
|
431
|
+
msg.message
|
|
432
|
+
for msg in self.messages
|
|
433
|
+
if msg.type == MessageType.WARNING and msg.message
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
return BrowserDebugResult(
|
|
437
|
+
status="success" if result["completed"] else "timeout",
|
|
438
|
+
port=port,
|
|
439
|
+
capabilities=capabilities,
|
|
440
|
+
messages=self.messages,
|
|
441
|
+
errors=errors,
|
|
442
|
+
warnings=warnings,
|
|
443
|
+
duration_seconds=result["duration"],
|
|
444
|
+
browser_opened=browser_opened,
|
|
445
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Data models for browser debug command."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from deepctl_core import BaseResult
|
|
8
|
+
from pydantic import BaseModel, Field, field_serializer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MessageType(str, Enum):
|
|
12
|
+
"""Types of messages from the browser debugger."""
|
|
13
|
+
|
|
14
|
+
CAPABILITY_CHECK = "capability_check"
|
|
15
|
+
ERROR = "error"
|
|
16
|
+
INFO = "info"
|
|
17
|
+
WARNING = "warning"
|
|
18
|
+
COMPLETE = "complete"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BrowserCapability(BaseModel):
|
|
22
|
+
"""Individual browser capability check result."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
supported: bool
|
|
26
|
+
version: str | None = None
|
|
27
|
+
details: str | None = None
|
|
28
|
+
required: bool = True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BrowserCapabilities(BaseModel):
|
|
32
|
+
"""All browser capability check results."""
|
|
33
|
+
|
|
34
|
+
web_audio_api: BrowserCapability
|
|
35
|
+
audio_context: BrowserCapability
|
|
36
|
+
audio_worklet: BrowserCapability
|
|
37
|
+
websocket_api: BrowserCapability
|
|
38
|
+
fetch_api: BrowserCapability
|
|
39
|
+
es6_features: BrowserCapability
|
|
40
|
+
dom_apis: BrowserCapability
|
|
41
|
+
console_api: BrowserCapability
|
|
42
|
+
timer_apis: BrowserCapability
|
|
43
|
+
secure_context: BrowserCapability
|
|
44
|
+
user_agent: str
|
|
45
|
+
overall_compatible: bool
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class WebSocketMessage(BaseModel):
|
|
49
|
+
"""Message received from the browser via WebSocket."""
|
|
50
|
+
|
|
51
|
+
type: MessageType
|
|
52
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
53
|
+
data: dict[str, Any]
|
|
54
|
+
message: str | None = None
|
|
55
|
+
|
|
56
|
+
@field_serializer("timestamp")
|
|
57
|
+
def serialize_timestamp(self, timestamp: datetime) -> str:
|
|
58
|
+
"""Serialize datetime to ISO format string."""
|
|
59
|
+
return timestamp.isoformat()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BrowserDebugResult(BaseResult):
|
|
63
|
+
"""Result from browser debug command execution."""
|
|
64
|
+
|
|
65
|
+
status: str = "success"
|
|
66
|
+
port: int
|
|
67
|
+
capabilities: BrowserCapabilities | None = None
|
|
68
|
+
messages: list[WebSocketMessage] = Field(default_factory=list)
|
|
69
|
+
errors: list[str] = Field(default_factory=list)
|
|
70
|
+
warnings: list[str] = Field(default_factory=list)
|
|
71
|
+
duration_seconds: float | None = None
|
|
72
|
+
browser_opened: bool = False
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Deepctl Browser Debug</title>
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
17
|
+
background: #0a0a0a;
|
|
18
|
+
color: #e0e0e0;
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
padding: 2rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.container {
|
|
26
|
+
max-width: 1000px;
|
|
27
|
+
margin: 0 auto;
|
|
28
|
+
width: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
h1 {
|
|
32
|
+
font-size: 2.5rem;
|
|
33
|
+
margin-bottom: 1rem;
|
|
34
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
35
|
+
-webkit-background-clip: text;
|
|
36
|
+
-webkit-text-fill-color: transparent;
|
|
37
|
+
background-clip: text;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.subtitle {
|
|
41
|
+
color: #888;
|
|
42
|
+
margin-bottom: 2rem;
|
|
43
|
+
font-size: 1.1rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.status {
|
|
47
|
+
background: #1a1a1a;
|
|
48
|
+
border-radius: 12px;
|
|
49
|
+
padding: 1.5rem;
|
|
50
|
+
margin-bottom: 2rem;
|
|
51
|
+
border: 1px solid #333;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.status-item {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 0.5rem;
|
|
58
|
+
margin-bottom: 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.indicator {
|
|
62
|
+
width: 12px;
|
|
63
|
+
height: 12px;
|
|
64
|
+
border-radius: 50%;
|
|
65
|
+
background: #444;
|
|
66
|
+
transition: all 0.3s ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.indicator.success {
|
|
70
|
+
background: #4ade80;
|
|
71
|
+
box-shadow: 0 0 10px rgba(74, 222, 128, 0.5);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.indicator.error {
|
|
75
|
+
background: #f87171;
|
|
76
|
+
box-shadow: 0 0 10px rgba(248, 113, 113, 0.5);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.indicator.warning {
|
|
80
|
+
background: #fbbf24;
|
|
81
|
+
box-shadow: 0 0 10px rgba(251, 191, 36, 0.5);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.checks {
|
|
85
|
+
display: grid;
|
|
86
|
+
gap: 1rem;
|
|
87
|
+
margin-bottom: 2rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.check-item {
|
|
91
|
+
background: #1a1a1a;
|
|
92
|
+
border: 1px solid #333;
|
|
93
|
+
border-radius: 8px;
|
|
94
|
+
padding: 1rem 1.5rem;
|
|
95
|
+
display: flex;
|
|
96
|
+
justify-content: space-between;
|
|
97
|
+
align-items: center;
|
|
98
|
+
transition: all 0.3s ease;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.check-item:hover {
|
|
102
|
+
border-color: #555;
|
|
103
|
+
background: #222;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.check-name {
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 0.5rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.check-status {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.5rem;
|
|
117
|
+
font-size: 0.9rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.check-status.success {
|
|
121
|
+
color: #4ade80;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.check-status.error {
|
|
125
|
+
color: #f87171;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.check-status.warning {
|
|
129
|
+
color: #fbbf24;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.progress {
|
|
133
|
+
width: 100%;
|
|
134
|
+
height: 6px;
|
|
135
|
+
background: #333;
|
|
136
|
+
border-radius: 3px;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
margin: 2rem 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.progress-bar {
|
|
142
|
+
height: 100%;
|
|
143
|
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
|
144
|
+
transition: width 0.3s ease;
|
|
145
|
+
width: 0%;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.log {
|
|
149
|
+
background: #0d0d0d;
|
|
150
|
+
border: 1px solid #333;
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
padding: 1rem;
|
|
153
|
+
max-height: 300px;
|
|
154
|
+
overflow-y: auto;
|
|
155
|
+
font-family: 'Courier New', monospace;
|
|
156
|
+
font-size: 0.9rem;
|
|
157
|
+
line-height: 1.5;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.log-entry {
|
|
161
|
+
margin-bottom: 0.5rem;
|
|
162
|
+
padding: 0.25rem 0;
|
|
163
|
+
border-bottom: 1px solid #222;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.log-entry:last-child {
|
|
167
|
+
border-bottom: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.timestamp {
|
|
171
|
+
color: #666;
|
|
172
|
+
font-size: 0.8rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.complete-message {
|
|
176
|
+
text-align: center;
|
|
177
|
+
padding: 2rem;
|
|
178
|
+
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
|
|
179
|
+
border-radius: 12px;
|
|
180
|
+
margin-top: 2rem;
|
|
181
|
+
display: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.complete-message.show {
|
|
185
|
+
display: block;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.loader {
|
|
189
|
+
width: 20px;
|
|
190
|
+
height: 20px;
|
|
191
|
+
border: 2px solid #333;
|
|
192
|
+
border-top-color: #667eea;
|
|
193
|
+
border-radius: 50%;
|
|
194
|
+
animation: spin 0.8s linear infinite;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@keyframes spin {
|
|
198
|
+
to {
|
|
199
|
+
transform: rotate(360deg);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
</style>
|
|
203
|
+
</head>
|
|
204
|
+
|
|
205
|
+
<body>
|
|
206
|
+
<div class="container">
|
|
207
|
+
<h1>Deepctl Browser Debug</h1>
|
|
208
|
+
<p class="subtitle">Running browser capability checks for Deepgram services...</p>
|
|
209
|
+
|
|
210
|
+
<div class="status">
|
|
211
|
+
<div class="status-item">
|
|
212
|
+
<div class="indicator" id="ws-indicator"></div>
|
|
213
|
+
<span>WebSocket Connection</span>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div class="progress">
|
|
218
|
+
<div class="progress-bar" id="progress-bar"></div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div class="checks" id="checks">
|
|
222
|
+
<!-- Capability checks will be inserted here -->
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<h3 style="margin-bottom: 1rem;">Debug Log</h3>
|
|
226
|
+
<div class="log" id="log"></div>
|
|
227
|
+
|
|
228
|
+
<div class="complete-message" id="complete-message">
|
|
229
|
+
<h2>Debug Complete!</h2>
|
|
230
|
+
<p style="margin-top: 1rem; color: #888;">You can close this window now.</p>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<script>
|
|
235
|
+
const WS_PORT = window.location.port;
|
|
236
|
+
const WS_URL = `ws://localhost:${WS_PORT}/ws`;
|
|
237
|
+
|
|
238
|
+
let ws = null;
|
|
239
|
+
let checkResults = {};
|
|
240
|
+
let totalChecks = 0;
|
|
241
|
+
let completedChecks = 0;
|
|
242
|
+
|
|
243
|
+
// Log function
|
|
244
|
+
function log(message, type = 'info') {
|
|
245
|
+
const logEl = document.getElementById('log');
|
|
246
|
+
const entry = document.createElement('div');
|
|
247
|
+
entry.className = 'log-entry';
|
|
248
|
+
|
|
249
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
250
|
+
entry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
|
|
251
|
+
|
|
252
|
+
logEl.appendChild(entry);
|
|
253
|
+
logEl.scrollTop = logEl.scrollHeight;
|
|
254
|
+
|
|
255
|
+
// Send to WebSocket
|
|
256
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
257
|
+
ws.send(JSON.stringify({
|
|
258
|
+
type: type,
|
|
259
|
+
message: message,
|
|
260
|
+
data: {}
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Update progress
|
|
266
|
+
function updateProgress() {
|
|
267
|
+
const progress = (completedChecks / totalChecks) * 100;
|
|
268
|
+
document.getElementById('progress-bar').style.width = `${progress}%`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Connect to WebSocket
|
|
272
|
+
function connectWebSocket() {
|
|
273
|
+
log('Connecting to debug server...');
|
|
274
|
+
|
|
275
|
+
ws = new WebSocket(WS_URL);
|
|
276
|
+
|
|
277
|
+
ws.onopen = () => {
|
|
278
|
+
log('Connected to debug server', 'info');
|
|
279
|
+
document.getElementById('ws-indicator').className = 'indicator success';
|
|
280
|
+
startDebugChecks();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
ws.onerror = (error) => {
|
|
284
|
+
log(`WebSocket error: ${error}`, 'error');
|
|
285
|
+
document.getElementById('ws-indicator').className = 'indicator error';
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
ws.onclose = () => {
|
|
289
|
+
log('Disconnected from debug server', 'info');
|
|
290
|
+
document.getElementById('ws-indicator').className = 'indicator error';
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check browser capabilities
|
|
295
|
+
function startDebugChecks() {
|
|
296
|
+
log('Starting browser capability checks...');
|
|
297
|
+
|
|
298
|
+
const checks = [
|
|
299
|
+
{
|
|
300
|
+
name: 'Web Audio API',
|
|
301
|
+
key: 'web_audio_api',
|
|
302
|
+
check: () => {
|
|
303
|
+
return typeof window.AudioContext !== 'undefined' ||
|
|
304
|
+
typeof window.webkitAudioContext !== 'undefined';
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'AudioContext',
|
|
309
|
+
key: 'audio_context',
|
|
310
|
+
check: () => {
|
|
311
|
+
try {
|
|
312
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
313
|
+
const ctx = new AudioCtx();
|
|
314
|
+
ctx.close();
|
|
315
|
+
return true;
|
|
316
|
+
} catch (e) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: 'AudioWorklet API',
|
|
323
|
+
key: 'audio_worklet',
|
|
324
|
+
check: async () => {
|
|
325
|
+
try {
|
|
326
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
327
|
+
const ctx = new AudioCtx();
|
|
328
|
+
const result = typeof ctx.audioWorklet !== 'undefined';
|
|
329
|
+
ctx.close();
|
|
330
|
+
return result;
|
|
331
|
+
} catch (e) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'WebSocket API',
|
|
338
|
+
key: 'websocket_api',
|
|
339
|
+
check: () => typeof WebSocket !== 'undefined'
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'Fetch API',
|
|
343
|
+
key: 'fetch_api',
|
|
344
|
+
check: () => typeof fetch !== 'undefined'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'ES6+ Features',
|
|
348
|
+
key: 'es6_features',
|
|
349
|
+
check: () => {
|
|
350
|
+
try {
|
|
351
|
+
// Test various ES6+ features
|
|
352
|
+
eval('const a = 1; let b = 2; ');
|
|
353
|
+
eval('const fn = async () => {}; ');
|
|
354
|
+
eval('const arr = [1,2,3]; const [...rest] = arr; ');
|
|
355
|
+
eval('class Test {}');
|
|
356
|
+
return true;
|
|
357
|
+
} catch (e) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'DOM APIs',
|
|
364
|
+
key: 'dom_apis',
|
|
365
|
+
check: () => {
|
|
366
|
+
return typeof document.createElement !== 'undefined' &&
|
|
367
|
+
typeof window.location !== 'undefined' &&
|
|
368
|
+
typeof document.head !== 'undefined';
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'Console API',
|
|
373
|
+
key: 'console_api',
|
|
374
|
+
check: () => {
|
|
375
|
+
return typeof console !== 'undefined' &&
|
|
376
|
+
typeof console.log === 'function' &&
|
|
377
|
+
typeof console.error === 'function';
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'Timer APIs',
|
|
382
|
+
key: 'timer_apis',
|
|
383
|
+
check: () => {
|
|
384
|
+
return typeof setTimeout === 'function' &&
|
|
385
|
+
typeof clearTimeout === 'function' &&
|
|
386
|
+
typeof setInterval === 'function';
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'Secure Context (HTTPS/localhost)',
|
|
391
|
+
key: 'secure_context',
|
|
392
|
+
check: () => window.isSecureContext === true
|
|
393
|
+
}
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
totalChecks = checks.length;
|
|
397
|
+
const checksContainer = document.getElementById('checks');
|
|
398
|
+
|
|
399
|
+
// Create UI for each check
|
|
400
|
+
checks.forEach(check => {
|
|
401
|
+
const checkEl = document.createElement('div');
|
|
402
|
+
checkEl.className = 'check-item';
|
|
403
|
+
checkEl.id = `check-${check.key}`;
|
|
404
|
+
checkEl.innerHTML = `
|
|
405
|
+
<div class="check-name">
|
|
406
|
+
<div class="loader" id="loader-${check.key}"></div>
|
|
407
|
+
${check.name}
|
|
408
|
+
</div>
|
|
409
|
+
<div class="check-status" id="status-${check.key}">
|
|
410
|
+
Checking...
|
|
411
|
+
</div>
|
|
412
|
+
`;
|
|
413
|
+
checksContainer.appendChild(checkEl);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Run checks
|
|
417
|
+
checks.forEach(async (check, index) => {
|
|
418
|
+
setTimeout(async () => {
|
|
419
|
+
try {
|
|
420
|
+
const result = await check.check();
|
|
421
|
+
const loaderEl = document.getElementById(`loader-${check.key}`);
|
|
422
|
+
const statusEl = document.getElementById(`status-${check.key}`);
|
|
423
|
+
|
|
424
|
+
checkResults[check.key] = {
|
|
425
|
+
name: check.name,
|
|
426
|
+
supported: result,
|
|
427
|
+
version: null,
|
|
428
|
+
details: result ? 'Supported' : 'Not supported',
|
|
429
|
+
required: true
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (result) {
|
|
433
|
+
loaderEl.className = 'indicator success';
|
|
434
|
+
statusEl.className = 'check-status success';
|
|
435
|
+
statusEl.textContent = '✓ Supported';
|
|
436
|
+
log(`${check.name}: Supported`, 'info');
|
|
437
|
+
} else {
|
|
438
|
+
loaderEl.className = 'indicator error';
|
|
439
|
+
statusEl.className = 'check-status error';
|
|
440
|
+
statusEl.textContent = '✗ Not Supported';
|
|
441
|
+
log(`${check.name}: Not supported`, 'error');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
completedChecks++;
|
|
445
|
+
updateProgress();
|
|
446
|
+
|
|
447
|
+
// Send result to server
|
|
448
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
449
|
+
ws.send(JSON.stringify({
|
|
450
|
+
type: 'capability_check',
|
|
451
|
+
data: {
|
|
452
|
+
capability: check.key,
|
|
453
|
+
result: checkResults[check.key]
|
|
454
|
+
}
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check if all tests are complete
|
|
459
|
+
if (completedChecks === totalChecks) {
|
|
460
|
+
completeDebug();
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
log(`Error checking ${check.name}: ${error.message}`, 'error');
|
|
464
|
+
completedChecks++;
|
|
465
|
+
updateProgress();
|
|
466
|
+
}
|
|
467
|
+
}, index * 200); // Stagger checks for visual effect
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Complete debug session
|
|
472
|
+
function completeDebug() {
|
|
473
|
+
log('All capability checks complete!', 'info');
|
|
474
|
+
|
|
475
|
+
const overallCompatible = Object.values(checkResults).every(r => r.supported);
|
|
476
|
+
|
|
477
|
+
// Send final results
|
|
478
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
479
|
+
ws.send(JSON.stringify({
|
|
480
|
+
type: 'complete',
|
|
481
|
+
data: {
|
|
482
|
+
capabilities: {
|
|
483
|
+
...checkResults,
|
|
484
|
+
user_agent: navigator.userAgent,
|
|
485
|
+
overall_compatible: overallCompatible
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}));
|
|
489
|
+
|
|
490
|
+
// Close WebSocket after a short delay
|
|
491
|
+
setTimeout(() => {
|
|
492
|
+
ws.close();
|
|
493
|
+
}, 1000);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Show completion message
|
|
497
|
+
document.getElementById('complete-message').className = 'complete-message show';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Initialize
|
|
501
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
502
|
+
connectWebSocket();
|
|
503
|
+
});
|
|
504
|
+
</script>
|
|
505
|
+
</body>
|
|
506
|
+
|
|
507
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-debug-browser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Browser debug subcommand for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,debug,browser,diagnostics
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: rich>=13.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: httpx>=0.24.0
|
|
22
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# deepctl-cmd-debug-browser
|
|
28
|
+
|
|
29
|
+
Browser debug subcommand for the Deepgram CLI (deepctl). This package provides the `browser` subcommand under the `debug` command group, allowing users to debug browser-related issues when using Deepgram's web-based features
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/deepctl_cmd_debug_browser/__init__.py
|
|
4
|
+
src/deepctl_cmd_debug_browser/command.py
|
|
5
|
+
src/deepctl_cmd_debug_browser/models.py
|
|
6
|
+
src/deepctl_cmd_debug_browser.egg-info/PKG-INFO
|
|
7
|
+
src/deepctl_cmd_debug_browser.egg-info/SOURCES.txt
|
|
8
|
+
src/deepctl_cmd_debug_browser.egg-info/dependency_links.txt
|
|
9
|
+
src/deepctl_cmd_debug_browser.egg-info/entry_points.txt
|
|
10
|
+
src/deepctl_cmd_debug_browser.egg-info/requires.txt
|
|
11
|
+
src/deepctl_cmd_debug_browser.egg-info/top_level.txt
|
|
12
|
+
src/deepctl_cmd_debug_browser/static/__init__.py
|
|
13
|
+
src/deepctl_cmd_debug_browser/static/debug.html
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_cmd_debug_browser
|