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.
@@ -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,3 @@
1
+ # deepctl-cmd-debug-browser
2
+
3
+ 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ # This file makes the static directory a Python package for
2
+ # importlib.resources compatibility
@@ -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,2 @@
1
+ [deepctl.subcommands.debug]
2
+ browser = deepctl_cmd_debug_browser.command:BrowserCommand
@@ -0,0 +1,10 @@
1
+ deepctl-core>=0.1.0
2
+ click>=8.0.0
3
+ rich>=13.0.0
4
+ pydantic>=2.0.0
5
+ httpx>=0.24.0
6
+ aiohttp>=3.9.0
7
+
8
+ [dev]
9
+ pytest>=7.0.0
10
+ pytest-asyncio>=0.21.0