webtap-tool 0.1.3__tar.gz → 0.1.4__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.

Potentially problematic release.


This version of webtap-tool might be problematic. Click here for more details.

Files changed (50) hide show
  1. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/CHANGELOG.md +22 -0
  2. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/PKG-INFO +1 -1
  3. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/extension/popup.js +28 -17
  4. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/pyproject.toml +1 -1
  5. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/__init__.py +5 -1
  6. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/api.py +65 -58
  7. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/app.py +15 -0
  8. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/.gitignore +0 -0
  9. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/ARCHITECTURE.md +0 -0
  10. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/README.md +0 -0
  11. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/data/filters.json +0 -0
  12. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/extension/manifest.json +0 -0
  13. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/extension/popup.html +0 -0
  14. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/llms.txt +0 -0
  15. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/VISION.md +0 -0
  16. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/README.md +0 -0
  17. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/__init__.py +0 -0
  18. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/query.py +0 -0
  19. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/schema/README.md +0 -0
  20. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/schema/cdp_protocol.json +0 -0
  21. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/schema/cdp_version.json +0 -0
  22. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/cdp/session.py +0 -0
  23. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/DEVELOPER_GUIDE.md +0 -0
  24. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/TIPS.md +0 -0
  25. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/__init__.py +0 -0
  26. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/_builders.py +0 -0
  27. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/_errors.py +0 -0
  28. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/_tips.py +0 -0
  29. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/_utils.py +0 -0
  30. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/body.py +0 -0
  31. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/connection.py +0 -0
  32. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/console.py +0 -0
  33. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/events.py +0 -0
  34. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/fetch.py +0 -0
  35. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/filters.py +0 -0
  36. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/inspect.py +0 -0
  37. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/javascript.py +0 -0
  38. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/launch.py +0 -0
  39. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/navigation.py +0 -0
  40. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/network.py +0 -0
  41. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/commands/setup.py +0 -0
  42. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/filters.py +0 -0
  43. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/README.md +0 -0
  44. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/__init__.py +0 -0
  45. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/body.py +0 -0
  46. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/console.py +0 -0
  47. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/fetch.py +0 -0
  48. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/main.py +0 -0
  49. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/network.py +0 -0
  50. {webtap_tool-0.1.3 → webtap_tool-0.1.4}/src/webtap/services/setup.py +0 -0
@@ -15,6 +15,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
 
16
16
  ### Removed
17
17
 
18
+ ## [0.1.4] - 2025-09-08
19
+
20
+ ### Added
21
+ - Request timeout handling (3 seconds) in Chrome extension for better error detection
22
+ - Comprehensive `cleanup()` method for proper resource management on exit
23
+ - `atexit` registration to ensure cleanup happens even on unexpected termination
24
+
25
+ ### Changed
26
+ - **API Consolidation**: Merged `/pages` and `/instance` endpoints into single `/info` endpoint
27
+ - Enhanced `/status` endpoint to include fetch details inline (eliminates separate API call)
28
+ - Improved error messages in extension to distinguish between timeout, server not running, and other failures
29
+ - API server shutdown now uses `asyncio.run()` for proper task cleanup
30
+
31
+ ### Fixed
32
+ - Graceful shutdown issue that left zombie tasks with "Task was destroyed but it is pending" error
33
+ - API server cleanup on application exit now properly cancels all async tasks
34
+ - Extension error handling now provides specific feedback for different failure modes
35
+
36
+ ### Removed
37
+ - `/instance` endpoint (functionality merged into `/info`)
38
+ - `/fetch/paused` endpoint (data now included in `/status` response)
39
+
18
40
  ## [0.1.3] - 2025-09-05
19
41
 
20
42
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webtap-tool
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -1,14 +1,28 @@
1
1
  // API helper - communicate with WebTap service
2
2
  async function api(endpoint, method = "GET", body = null) {
3
3
  try {
4
- const opts = { method };
4
+ const opts = {
5
+ method,
6
+ // Add timeout to detect unresponsive server faster
7
+ signal: AbortSignal.timeout(3000)
8
+ };
5
9
  if (body) {
6
10
  opts.headers = { "Content-Type": "application/json" };
7
11
  opts.body = JSON.stringify(body);
8
12
  }
9
13
  const resp = await fetch(`http://localhost:8765${endpoint}`, opts);
14
+ if (!resp.ok) {
15
+ return { error: `HTTP ${resp.status}: ${resp.statusText}` };
16
+ }
10
17
  return await resp.json();
11
18
  } catch (e) {
19
+ // Better error messages
20
+ if (e.name === 'AbortError') {
21
+ return { error: "WebTap not responding (timeout)" };
22
+ }
23
+ if (e.message.includes('Failed to fetch')) {
24
+ return { error: "WebTap not running" };
25
+ }
12
26
  return { error: e.message };
13
27
  }
14
28
  }
@@ -17,22 +31,20 @@ async function api(endpoint, method = "GET", body = null) {
17
31
 
18
32
  // Load available pages from WebTap
19
33
  async function loadPages() {
20
- const result = await api("/pages");
21
-
22
- if (result.error) {
34
+ // Use combined /info endpoint for single round trip
35
+ const info = await api("/info");
36
+
37
+ if (info.error) {
23
38
  document.getElementById("pageList").innerHTML =
24
- "<option disabled>WebTap not running</option>";
39
+ `<option disabled>${info.error === "WebTap not initialized" ? "WebTap not running" : "Error loading pages"}</option>`;
25
40
  return;
26
41
  }
27
42
 
28
- // Also fetch instance info if available
29
- const instanceInfo = await api("/instance");
30
- if (instanceInfo && !instanceInfo.error) {
31
- document.getElementById("switchInstance").title =
32
- `PID: ${instanceInfo.pid} | Events: ${instanceInfo.events}`;
33
- }
43
+ // Update instance info tooltip
44
+ document.getElementById("switchInstance").title =
45
+ `PID: ${info.pid} | Events: ${info.events}`;
34
46
 
35
- const pages = result.pages || [];
47
+ const pages = info.pages || [];
36
48
  const select = document.getElementById("pageList");
37
49
 
38
50
  select.innerHTML = "";
@@ -296,13 +308,12 @@ async function updateStatus() {
296
308
  document.getElementById("status").innerHTML =
297
309
  `<span class="connected">Connected</span> - Events: ${status.events}`;
298
310
 
299
- // Get fetch details if enabled
300
- if (status.fetch_enabled) {
301
- const fetchDetails = await api("/fetch/paused");
311
+ // Use fetch details from enhanced status (no extra API call needed)
312
+ if (status.fetch_enabled && status.fetch_details) {
302
313
  updateFetchStatus(
303
314
  true,
304
- status.paused_requests || 0,
305
- fetchDetails.response_stage || false,
315
+ status.fetch_details.paused_count || 0,
316
+ status.fetch_details.response_stage || false,
306
317
  );
307
318
  } else {
308
319
  updateFetchStatus(false);
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webtap-tool"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "Terminal-based web page inspector for AI debugging sessions"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -9,6 +9,7 @@ PUBLIC API:
9
9
  - main: Entry point function for CLI
10
10
  """
11
11
 
12
+ import atexit
12
13
  import sys
13
14
  import logging
14
15
 
@@ -45,12 +46,15 @@ def main():
45
46
 
46
47
 
47
48
  def _start_api_server_safely():
48
- """Start API server with error handling."""
49
+ """Start API server with error handling and cleanup registration."""
49
50
  try:
50
51
  thread = start_api_server(app.state)
51
52
  if thread and app.state:
52
53
  app.state.api_thread = thread
53
54
  logger.info("API server started on port 8765")
55
+
56
+ # Register cleanup to shut down API server on exit
57
+ atexit.register(lambda: app.state.cleanup() if app.state else None)
54
58
  else:
55
59
  logger.info("Port 8765 in use by another instance")
56
60
  except Exception as e:
@@ -50,22 +50,54 @@ api.add_middleware(
50
50
  app_state = None
51
51
 
52
52
 
53
- @api.get("/pages")
54
- async def list_pages() -> Dict[str, Any]:
55
- """List available Chrome pages for extension selection."""
53
+ @api.get("/health")
54
+ async def health_check() -> Dict[str, Any]:
55
+ """Quick health check endpoint for extension."""
56
+ return {"status": "ok", "pid": os.getpid()}
57
+
58
+
59
+ @api.get("/info")
60
+ async def get_info() -> Dict[str, Any]:
61
+ """Combined endpoint for pages and instance info - reduces round trips."""
56
62
  if not app_state:
57
- return {"error": "WebTap not initialized", "pages": []}
63
+ return {"error": "WebTap not initialized", "pages": [], "pid": os.getpid()}
64
+
65
+ # Get pages
66
+ pages_data = app_state.service.list_pages()
67
+
68
+ # Get instance info
69
+ connected_to = None
70
+ if app_state.cdp.is_connected and app_state.cdp.page_info:
71
+ connected_to = app_state.cdp.page_info.get("title", "Untitled")
58
72
 
59
- return app_state.service.list_pages()
73
+ return {
74
+ "pid": os.getpid(),
75
+ "connected_to": connected_to,
76
+ "events": app_state.service.event_count,
77
+ "pages": pages_data.get("pages", []),
78
+ "error": pages_data.get("error"),
79
+ }
60
80
 
61
81
 
62
82
  @api.get("/status")
63
83
  async def get_status() -> Dict[str, Any]:
64
- """Get current connection status and event count."""
84
+ """Get comprehensive status including connection, events, and fetch details."""
65
85
  if not app_state:
66
86
  return {"connected": False, "error": "WebTap not initialized", "events": 0}
67
87
 
68
- return app_state.service.get_status()
88
+ status = app_state.service.get_status()
89
+
90
+ # Add fetch details if fetch is enabled
91
+ if status.get("fetch_enabled"):
92
+ fetch_service = app_state.service.fetch
93
+ paused_list = fetch_service.get_paused_list()
94
+ status["fetch_details"] = {
95
+ "paused_requests": paused_list,
96
+ "paused_count": len(paused_list),
97
+ "response_stage": fetch_service.enable_response_stage,
98
+ }
99
+
100
+ return status
69
101
 
70
102
 
71
103
  @api.post("/connect")
@@ -108,25 +140,6 @@ async def set_fetch_interception(request: FetchRequest) -> Dict[str, Any]:
108
140
  return result
109
141
 
110
142
 
111
- @api.get("/fetch/paused")
112
- async def get_paused_requests() -> Dict[str, Any]:
113
- """Get list of currently paused fetch requests."""
114
- if not app_state:
115
- return {"error": "WebTap not initialized", "requests": []}
116
-
117
- fetch_service = app_state.service.fetch
118
- if not fetch_service.enabled:
119
- return {"enabled": False, "requests": []}
120
-
121
- paused_list = fetch_service.get_paused_list()
122
- return {
123
- "enabled": True,
124
- "requests": paused_list,
125
- "count": len(paused_list),
126
- "response_stage": fetch_service.enable_response_stage,
127
- }
128
-
129
-
130
143
  @api.get("/filters/status")
131
144
  async def get_filter_status() -> Dict[str, Any]:
132
145
  """Get current filter configuration and enabled categories."""
@@ -186,19 +199,6 @@ async def disable_all_filters() -> Dict[str, Any]:
186
199
  return {"enabled": [], "total": 0}
187
200
 
188
201
 
189
- @api.get("/instance")
190
- async def get_instance_info() -> Dict[str, Any]:
191
- """Get info about this WebTap instance."""
192
- if not app_state:
193
- return {"error": "WebTap not initialized"}
194
-
195
- return {
196
- "pid": os.getpid(),
197
- "connected_to": app_state.cdp.current_page_title if app_state.cdp.is_connected else None,
198
- "events": app_state.cdp.event_count,
199
- }
200
-
201
-
202
202
  @api.post("/release")
203
203
  async def release_port() -> Dict[str, Any]:
204
204
  """Release API port for another WebTap instance."""
@@ -237,8 +237,9 @@ def start_api_server(state, host: str = "127.0.0.1", port: int = 8765) -> thread
237
237
  logger.info(f"Port {port} already in use")
238
238
  return None
239
239
 
240
- global app_state
240
+ global app_state, _shutdown_requested
241
241
  app_state = state
242
+ _shutdown_requested = False # Reset flag for new instance
242
243
 
243
244
  thread = threading.Thread(target=run_server, args=(host, port), daemon=True)
244
245
  thread.start()
@@ -249,7 +250,10 @@ def start_api_server(state, host: str = "127.0.0.1", port: int = 8765) -> thread
249
250
 
250
251
  def run_server(host: str, port: int):
251
252
  """Run the FastAPI server in a thread."""
252
- try:
253
+ import asyncio
254
+
255
+ async def run():
256
+ """Run server with proper shutdown handling."""
253
257
  config = uvicorn.Config(
254
258
  api,
255
259
  host=host,
@@ -259,30 +263,33 @@ def run_server(host: str, port: int):
259
263
  )
260
264
  server = uvicorn.Server(config)
261
265
 
262
- # Run with checking for shutdown flag
263
- import asyncio
264
-
265
- loop = asyncio.new_event_loop()
266
- asyncio.set_event_loop(loop)
266
+ # Start server in background task
267
+ serve_task = asyncio.create_task(server.serve())
267
268
 
268
- async def serve():
269
- await server.serve()
270
-
271
- # Start serving
272
- task = loop.create_task(serve())
273
-
274
- # Check for shutdown flag
269
+ # Wait for shutdown signal
275
270
  while not _shutdown_requested:
276
- loop.run_until_complete(asyncio.sleep(0.1))
277
- if task.done():
271
+ await asyncio.sleep(0.1)
272
+ if serve_task.done():
278
273
  break
279
274
 
280
- # Shutdown if requested
281
- if _shutdown_requested:
275
+ # Trigger shutdown
276
+ if not serve_task.done():
282
277
  logger.info("API server shutting down")
283
278
  server.should_exit = True
284
- loop.run_until_complete(server.shutdown())
279
+ # Wait a bit for graceful shutdown
280
+ try:
281
+ await asyncio.wait_for(serve_task, timeout=1.0)
282
+ except asyncio.TimeoutError:
283
+ logger.debug("Server task timeout - cancelling")
284
+ serve_task.cancel()
285
+ try:
286
+ await serve_task
287
+ except asyncio.CancelledError:
288
+ pass
285
289
 
290
+ try:
291
+ # Use asyncio.run() which properly cleans up
292
+ asyncio.run(run())
286
293
  except Exception as e:
287
294
  logger.error(f"API server failed: {e}")
288
295
 
@@ -36,6 +36,21 @@ class WebTapState:
36
36
  """Initialize service with self reference after dataclass init."""
37
37
  self.service = WebTapService(self)
38
38
 
39
+ def cleanup(self):
40
+ """Cleanup resources on exit."""
41
+ # Disconnect CDP if connected
42
+ if self.cdp.is_connected:
43
+ self.cdp.disconnect()
44
+
45
+ # Stop API server if we own it
46
+ if self.api_thread and self.api_thread.is_alive():
47
+ # Import here to avoid circular dependency
48
+ import webtap.api
49
+
50
+ webtap.api._shutdown_requested = True
51
+ # Wait up to 1 second for graceful shutdown
52
+ self.api_thread.join(timeout=1.0)
53
+
39
54
 
40
55
  # Must be created before command imports for decorator registration
41
56
  app = App(
File without changes
File without changes
File without changes
File without changes