webtap-tool 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webtap-tool might be problematic. Click here for more details.
- webtap/VISION.md +234 -0
- webtap/__init__.py +56 -0
- webtap/api.py +222 -0
- webtap/app.py +76 -0
- webtap/cdp/README.md +268 -0
- webtap/cdp/__init__.py +14 -0
- webtap/cdp/query.py +107 -0
- webtap/cdp/schema/README.md +41 -0
- webtap/cdp/schema/cdp_protocol.json +32785 -0
- webtap/cdp/schema/cdp_version.json +8 -0
- webtap/cdp/session.py +365 -0
- webtap/commands/DEVELOPER_GUIDE.md +314 -0
- webtap/commands/TIPS.md +153 -0
- webtap/commands/__init__.py +7 -0
- webtap/commands/_builders.py +127 -0
- webtap/commands/_errors.py +108 -0
- webtap/commands/_tips.py +147 -0
- webtap/commands/_utils.py +227 -0
- webtap/commands/body.py +161 -0
- webtap/commands/connection.py +168 -0
- webtap/commands/console.py +69 -0
- webtap/commands/events.py +109 -0
- webtap/commands/fetch.py +219 -0
- webtap/commands/filters.py +224 -0
- webtap/commands/inspect.py +146 -0
- webtap/commands/javascript.py +87 -0
- webtap/commands/launch.py +86 -0
- webtap/commands/navigation.py +199 -0
- webtap/commands/network.py +85 -0
- webtap/commands/setup.py +127 -0
- webtap/filters.py +289 -0
- webtap/services/README.md +83 -0
- webtap/services/__init__.py +15 -0
- webtap/services/body.py +113 -0
- webtap/services/console.py +116 -0
- webtap/services/fetch.py +397 -0
- webtap/services/main.py +175 -0
- webtap/services/network.py +105 -0
- webtap/services/setup.py +219 -0
- webtap_tool-0.1.1.dist-info/METADATA +427 -0
- webtap_tool-0.1.1.dist-info/RECORD +43 -0
- webtap_tool-0.1.1.dist-info/WHEEL +4 -0
- webtap_tool-0.1.1.dist-info/entry_points.txt +2 -0
webtap/VISION.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# WebTap Vision: Work WITH Chrome DevTools Protocol
|
|
2
|
+
|
|
3
|
+
## Core Philosophy
|
|
4
|
+
|
|
5
|
+
**Store CDP events as-is. Transform minimally. Query on-demand.**
|
|
6
|
+
|
|
7
|
+
Instead of transforming CDP's complex nested structures into our own models, we embrace CDP's native format. We store events mostly unchanged, extract minimal data for tables, and query additional data on-demand.
|
|
8
|
+
|
|
9
|
+
## The Problem We're Solving
|
|
10
|
+
|
|
11
|
+
CDP sends rich, nested event data. Previous approaches tried to:
|
|
12
|
+
1. Transform everything into flat models
|
|
13
|
+
2. Create abstraction layers over CDP
|
|
14
|
+
3. Build complex query engines
|
|
15
|
+
4. Format data for display
|
|
16
|
+
|
|
17
|
+
This led to:
|
|
18
|
+
- Loss of CDP's rich information
|
|
19
|
+
- Complex transformation logic
|
|
20
|
+
- Over-engineered abstractions
|
|
21
|
+
- Unnecessary memory usage
|
|
22
|
+
|
|
23
|
+
## The Solution: Native CDP Storage
|
|
24
|
+
|
|
25
|
+
### 1. Store Events As-Is
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# CDP gives us this - we keep it!
|
|
29
|
+
{
|
|
30
|
+
"method": "Network.responseReceived",
|
|
31
|
+
"params": {
|
|
32
|
+
"requestId": "123.456",
|
|
33
|
+
"response": {
|
|
34
|
+
"status": 200,
|
|
35
|
+
"headers": {...},
|
|
36
|
+
"mimeType": "application/json",
|
|
37
|
+
"timing": {...}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Minimal Summaries for Tables
|
|
44
|
+
|
|
45
|
+
Extract only what's needed for table display:
|
|
46
|
+
```python
|
|
47
|
+
NetworkSummary(
|
|
48
|
+
id="123.456",
|
|
49
|
+
method="GET",
|
|
50
|
+
status=200,
|
|
51
|
+
url="https://api.example.com/data",
|
|
52
|
+
type="json",
|
|
53
|
+
size=1234
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Keep the full CDP events attached for detail views.
|
|
58
|
+
|
|
59
|
+
### 3. On-Demand Queries
|
|
60
|
+
|
|
61
|
+
Some data isn't in the event stream:
|
|
62
|
+
- Response bodies: `Network.getResponseBody`
|
|
63
|
+
- Cookies: `Storage.getCookies`
|
|
64
|
+
- LocalStorage: `DOMStorage.getDOMStorageItems`
|
|
65
|
+
- JavaScript evaluation: `Runtime.evaluate`
|
|
66
|
+
|
|
67
|
+
Query these when needed, not preemptively.
|
|
68
|
+
|
|
69
|
+
## Architecture
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
┌─────────────────┐
|
|
73
|
+
│ Chrome Tab │
|
|
74
|
+
└────────┬────────┘
|
|
75
|
+
│ CDP Events
|
|
76
|
+
┌────────▼────────┐
|
|
77
|
+
│ WebSocket │
|
|
78
|
+
│ (WebSocketApp) │
|
|
79
|
+
└────────┬────────┘
|
|
80
|
+
│ Raw Events
|
|
81
|
+
┌────────▼────────┐
|
|
82
|
+
│ DuckDB Storage │
|
|
83
|
+
│ (events table) │
|
|
84
|
+
└────────┬────────┘
|
|
85
|
+
│ SQL Queries
|
|
86
|
+
┌────────────┼────────────┐
|
|
87
|
+
│ │ │
|
|
88
|
+
┌───────▼──────┐ ┌───▼───┐ ┌──────▼──────┐
|
|
89
|
+
│ Commands │ │ Tables│ │Detail Views │
|
|
90
|
+
│network() │ │ │ │ │
|
|
91
|
+
│console() │ │Minimal│ │Full CDP Data│
|
|
92
|
+
│storage() │ │Summary│ │+ On-Demand │
|
|
93
|
+
└──────────────┘ └───────┘ └─────────────┘
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Data Flow Examples
|
|
97
|
+
|
|
98
|
+
### Network Request Lifecycle
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# 1. Request sent - store as-is in DuckDB
|
|
102
|
+
db.execute("INSERT INTO events VALUES (?)", [json.dumps({
|
|
103
|
+
"method": "Network.requestWillBeSent",
|
|
104
|
+
"params": {...} # Full CDP data
|
|
105
|
+
})])
|
|
106
|
+
|
|
107
|
+
# 2. Response received - store as-is
|
|
108
|
+
db.execute("INSERT INTO events VALUES (?)", [json.dumps({
|
|
109
|
+
"method": "Network.responseReceived",
|
|
110
|
+
"params": {...} # Full CDP data
|
|
111
|
+
})])
|
|
112
|
+
|
|
113
|
+
# 3. Query for table view - SQL on JSON
|
|
114
|
+
db.execute("""
|
|
115
|
+
SELECT
|
|
116
|
+
json_extract_string(event, '$.params.requestId') as id,
|
|
117
|
+
json_extract_string(event, '$.params.response.status') as status,
|
|
118
|
+
json_extract_string(event, '$.params.response.url') as url
|
|
119
|
+
FROM events
|
|
120
|
+
WHERE json_extract_string(event, '$.method') = 'Network.responseReceived'
|
|
121
|
+
""")
|
|
122
|
+
|
|
123
|
+
# 4. Detail view - get all events for request
|
|
124
|
+
db.execute("""
|
|
125
|
+
SELECT event FROM events
|
|
126
|
+
WHERE json_extract_string(event, '$.params.requestId') = ?
|
|
127
|
+
""", [request_id])
|
|
128
|
+
|
|
129
|
+
# 5. Body fetch - on-demand CDP call
|
|
130
|
+
cdp.execute("Network.getResponseBody", {"requestId": "123.456"})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Console Message
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# 1. Store as-is
|
|
137
|
+
console_events.append({
|
|
138
|
+
"method": "Runtime.consoleAPICalled",
|
|
139
|
+
"params": {
|
|
140
|
+
"type": "error",
|
|
141
|
+
"args": [{"type": "string", "value": "Failed to fetch"}],
|
|
142
|
+
"stackTrace": {...}
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
# 2. Table view - minimal summary
|
|
147
|
+
ConsoleSummary(
|
|
148
|
+
id="console-123",
|
|
149
|
+
level="error",
|
|
150
|
+
message="Failed to fetch",
|
|
151
|
+
source="console"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# 3. Detail view - full CDP data
|
|
155
|
+
{
|
|
156
|
+
"summary": summary,
|
|
157
|
+
"raw": console_events[i], # Full CDP event with stack trace
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Benefits
|
|
162
|
+
|
|
163
|
+
1. **No Information Loss** - Full CDP data always available
|
|
164
|
+
2. **Minimal Memory** - Only store what CDP sends
|
|
165
|
+
3. **Simple Code** - No complex transformations
|
|
166
|
+
4. **Fast Tables** - Minimal summaries render quickly
|
|
167
|
+
5. **Rich Details** - Full CDP data for debugging
|
|
168
|
+
6. **On-Demand Loading** - Expensive operations only when needed
|
|
169
|
+
7. **Future Proof** - New CDP features automatically available
|
|
170
|
+
|
|
171
|
+
## Implementation Principles
|
|
172
|
+
|
|
173
|
+
### DO:
|
|
174
|
+
- Store CDP events as-is
|
|
175
|
+
- Build minimal summaries for tables
|
|
176
|
+
- Query additional data on-demand
|
|
177
|
+
- Group events by correlation ID (requestId)
|
|
178
|
+
- Let Replkit2 handle display
|
|
179
|
+
|
|
180
|
+
### DON'T:
|
|
181
|
+
- Transform CDP structure unnecessarily
|
|
182
|
+
- Fetch data preemptively
|
|
183
|
+
- Create abstraction layers
|
|
184
|
+
- Build complex query engines
|
|
185
|
+
- Format data for display
|
|
186
|
+
|
|
187
|
+
## File Structure
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
webtap/
|
|
191
|
+
├── VISION.md # This file
|
|
192
|
+
├── __init__.py # Module initialization + API server startup
|
|
193
|
+
├── app.py # REPL app with WebTapState
|
|
194
|
+
├── api.py # FastAPI server for Chrome extension
|
|
195
|
+
├── filters.py # Filter management system
|
|
196
|
+
├── cdp/
|
|
197
|
+
│ ├── __init__.py
|
|
198
|
+
│ ├── session.py # CDPSession with DuckDB storage
|
|
199
|
+
│ ├── query.py # Dynamic query builder
|
|
200
|
+
│ └── schema/ # CDP protocol reference
|
|
201
|
+
│ ├── README.md
|
|
202
|
+
│ └── cdp_version.json
|
|
203
|
+
├── services/ # Service layer (business logic)
|
|
204
|
+
│ ├── __init__.py
|
|
205
|
+
│ ├── main.py # WebTapService orchestrator
|
|
206
|
+
│ ├── network.py # Network request handling
|
|
207
|
+
│ ├── console.py # Console message handling
|
|
208
|
+
│ ├── fetch.py # Request interception
|
|
209
|
+
│ └── body.py # Response body caching
|
|
210
|
+
└── commands/ # Thin command wrappers
|
|
211
|
+
├── __init__.py
|
|
212
|
+
├── _errors.py # Unified error handling
|
|
213
|
+
├── _markdown.py # Markdown elements
|
|
214
|
+
├── _symbols.py # ASCII symbol registry
|
|
215
|
+
├── _utils.py # Shared utilities
|
|
216
|
+
├── connection.py # connect, disconnect, pages
|
|
217
|
+
├── navigation.py # navigate, reload, back, forward
|
|
218
|
+
├── network.py # network() command
|
|
219
|
+
├── console.py # console() command
|
|
220
|
+
├── events.py # events() dynamic querying
|
|
221
|
+
├── inspect.py # inspect() event details
|
|
222
|
+
├── javascript.py # js() execution
|
|
223
|
+
├── body.py # body() response inspection
|
|
224
|
+
├── fetch.py # fetch(), requests(), resume()
|
|
225
|
+
└── filters.py # filters() management
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Success Metrics
|
|
229
|
+
|
|
230
|
+
- **Lines of Code**: < 500 (excluding commands)
|
|
231
|
+
- **Transformation Logic**: < 100 lines
|
|
232
|
+
- **Memory Usage**: Only what CDP sends
|
|
233
|
+
- **Response Time**: Instant for tables, < 100ms for details
|
|
234
|
+
- **CDP Coverage**: 100% of CDP data accessible
|
webtap/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""WebTap - Chrome DevTools Protocol REPL.
|
|
2
|
+
|
|
3
|
+
Main entry point for WebTap browser debugging tool. Provides both REPL and MCP
|
|
4
|
+
functionality for Chrome DevTools Protocol interaction with native CDP event
|
|
5
|
+
storage and on-demand querying.
|
|
6
|
+
|
|
7
|
+
PUBLIC API:
|
|
8
|
+
- app: Main ReplKit2 App instance
|
|
9
|
+
- main: Entry point function for CLI
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
from webtap.app import app
|
|
16
|
+
from webtap.api import start_api_server
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
"""Entry point for the WebTap REPL.
|
|
23
|
+
|
|
24
|
+
Starts in one of three modes:
|
|
25
|
+
- CLI mode (with --cli flag) for command-line interface
|
|
26
|
+
- MCP mode (with --mcp flag) for Model Context Protocol server
|
|
27
|
+
- REPL mode (default) for interactive shell
|
|
28
|
+
|
|
29
|
+
In REPL and MCP modes, the API server is started for Chrome extension
|
|
30
|
+
integration. The API server runs in background to handle extension requests.
|
|
31
|
+
"""
|
|
32
|
+
# Start API server for Chrome extension (except in CLI mode)
|
|
33
|
+
if "--cli" not in sys.argv:
|
|
34
|
+
_start_api_server_safely()
|
|
35
|
+
|
|
36
|
+
if "--mcp" in sys.argv:
|
|
37
|
+
app.mcp.run()
|
|
38
|
+
elif "--cli" in sys.argv:
|
|
39
|
+
# Remove --cli from argv before passing to Typer
|
|
40
|
+
sys.argv.remove("--cli")
|
|
41
|
+
app.cli() # Run CLI mode via Typer
|
|
42
|
+
else:
|
|
43
|
+
# Run REPL
|
|
44
|
+
app.run(title="WebTap - Chrome DevTools Protocol REPL")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _start_api_server_safely():
|
|
48
|
+
"""Start API server with error handling."""
|
|
49
|
+
try:
|
|
50
|
+
start_api_server(app.state)
|
|
51
|
+
logger.info("API server started on http://localhost:8765")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.warning(f"Failed to start API server: {e}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ["app", "main"]
|
webtap/api.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""FastAPI endpoints for WebTap browser extension.
|
|
2
|
+
|
|
3
|
+
PUBLIC API:
|
|
4
|
+
- start_api_server: Start API server in background thread
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
import uvicorn
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Request models
|
|
21
|
+
class ConnectRequest(BaseModel):
|
|
22
|
+
"""Request model for connecting to a Chrome page."""
|
|
23
|
+
|
|
24
|
+
page_id: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FetchRequest(BaseModel):
|
|
28
|
+
"""Request model for enabling/disabling fetch interception."""
|
|
29
|
+
|
|
30
|
+
enabled: bool
|
|
31
|
+
response_stage: bool = False # Optional: also pause at Response stage
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Create FastAPI app
|
|
35
|
+
api = FastAPI(title="WebTap API", version="0.1.0")
|
|
36
|
+
|
|
37
|
+
# Enable CORS for extension
|
|
38
|
+
api.add_middleware(
|
|
39
|
+
CORSMiddleware,
|
|
40
|
+
allow_origins=["*"], # Chrome extensions have unique origins
|
|
41
|
+
allow_credentials=True,
|
|
42
|
+
allow_methods=["*"],
|
|
43
|
+
allow_headers=["*"],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Global reference to WebTap state (set by start_api_server)
|
|
48
|
+
app_state = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@api.get("/pages")
|
|
52
|
+
async def list_pages() -> Dict[str, Any]:
|
|
53
|
+
"""List available Chrome pages for extension selection."""
|
|
54
|
+
if not app_state:
|
|
55
|
+
return {"error": "WebTap not initialized", "pages": []}
|
|
56
|
+
|
|
57
|
+
return app_state.service.list_pages()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@api.get("/status")
|
|
61
|
+
async def get_status() -> Dict[str, Any]:
|
|
62
|
+
"""Get current connection status and event count."""
|
|
63
|
+
if not app_state:
|
|
64
|
+
return {"connected": False, "error": "WebTap not initialized", "events": 0}
|
|
65
|
+
|
|
66
|
+
return app_state.service.get_status()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@api.post("/connect")
|
|
70
|
+
async def connect(request: ConnectRequest) -> Dict[str, Any]:
|
|
71
|
+
"""Connect to a Chrome page by stable page ID."""
|
|
72
|
+
if not app_state:
|
|
73
|
+
return {"error": "WebTap not initialized"}
|
|
74
|
+
|
|
75
|
+
return app_state.service.connect_to_page(page_id=request.page_id)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@api.post("/disconnect")
|
|
79
|
+
async def disconnect() -> Dict[str, Any]:
|
|
80
|
+
"""Disconnect from currently connected page."""
|
|
81
|
+
if not app_state:
|
|
82
|
+
return {"error": "WebTap not initialized"}
|
|
83
|
+
|
|
84
|
+
return app_state.service.disconnect()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@api.post("/clear")
|
|
88
|
+
async def clear_events() -> Dict[str, Any]:
|
|
89
|
+
"""Clear all stored events from DuckDB."""
|
|
90
|
+
if not app_state:
|
|
91
|
+
return {"error": "WebTap not initialized"}
|
|
92
|
+
|
|
93
|
+
return app_state.service.clear_events()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@api.post("/fetch")
|
|
97
|
+
async def set_fetch_interception(request: FetchRequest) -> Dict[str, Any]:
|
|
98
|
+
"""Enable or disable fetch request interception."""
|
|
99
|
+
if not app_state:
|
|
100
|
+
return {"error": "WebTap not initialized"}
|
|
101
|
+
|
|
102
|
+
if request.enabled:
|
|
103
|
+
result = app_state.service.fetch.enable(app_state.service.cdp, response_stage=request.response_stage)
|
|
104
|
+
else:
|
|
105
|
+
result = app_state.service.fetch.disable()
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@api.get("/fetch/paused")
|
|
110
|
+
async def get_paused_requests() -> Dict[str, Any]:
|
|
111
|
+
"""Get list of currently paused fetch requests."""
|
|
112
|
+
if not app_state:
|
|
113
|
+
return {"error": "WebTap not initialized", "requests": []}
|
|
114
|
+
|
|
115
|
+
fetch_service = app_state.service.fetch
|
|
116
|
+
if not fetch_service.enabled:
|
|
117
|
+
return {"enabled": False, "requests": []}
|
|
118
|
+
|
|
119
|
+
paused_list = fetch_service.get_paused_list()
|
|
120
|
+
return {
|
|
121
|
+
"enabled": True,
|
|
122
|
+
"requests": paused_list,
|
|
123
|
+
"count": len(paused_list),
|
|
124
|
+
"response_stage": fetch_service.enable_response_stage,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@api.get("/filters/status")
|
|
129
|
+
async def get_filter_status() -> Dict[str, Any]:
|
|
130
|
+
"""Get current filter configuration and enabled categories."""
|
|
131
|
+
if not app_state:
|
|
132
|
+
return {"error": "WebTap not initialized", "filters": {}, "enabled": []}
|
|
133
|
+
|
|
134
|
+
fm = app_state.service.filters
|
|
135
|
+
return {"filters": fm.filters, "enabled": list(fm.enabled_categories), "path": str(fm.filter_path)}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@api.post("/filters/toggle/{category}")
|
|
139
|
+
async def toggle_filter_category(category: str) -> Dict[str, Any]:
|
|
140
|
+
"""Toggle a specific filter category on or off."""
|
|
141
|
+
if not app_state:
|
|
142
|
+
return {"error": "WebTap not initialized"}
|
|
143
|
+
|
|
144
|
+
fm = app_state.service.filters
|
|
145
|
+
|
|
146
|
+
if category not in fm.filters:
|
|
147
|
+
return {"error": f"Category '{category}' not found"}
|
|
148
|
+
|
|
149
|
+
if category in fm.enabled_categories:
|
|
150
|
+
fm.enabled_categories.discard(category)
|
|
151
|
+
enabled = False
|
|
152
|
+
else:
|
|
153
|
+
fm.enabled_categories.add(category)
|
|
154
|
+
enabled = True
|
|
155
|
+
|
|
156
|
+
fm.save()
|
|
157
|
+
|
|
158
|
+
return {"category": category, "enabled": enabled, "total_enabled": len(fm.enabled_categories)}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@api.post("/filters/enable-all")
|
|
162
|
+
async def enable_all_filters() -> Dict[str, Any]:
|
|
163
|
+
"""Enable all available filter categories."""
|
|
164
|
+
if not app_state:
|
|
165
|
+
return {"error": "WebTap not initialized"}
|
|
166
|
+
|
|
167
|
+
fm = app_state.service.filters
|
|
168
|
+
fm.set_enabled_categories(None)
|
|
169
|
+
fm.save()
|
|
170
|
+
|
|
171
|
+
return {"enabled": list(fm.enabled_categories), "total": len(fm.enabled_categories)}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@api.post("/filters/disable-all")
|
|
175
|
+
async def disable_all_filters() -> Dict[str, Any]:
|
|
176
|
+
"""Disable all filter categories."""
|
|
177
|
+
if not app_state:
|
|
178
|
+
return {"error": "WebTap not initialized"}
|
|
179
|
+
|
|
180
|
+
fm = app_state.service.filters
|
|
181
|
+
fm.set_enabled_categories([])
|
|
182
|
+
fm.save()
|
|
183
|
+
|
|
184
|
+
return {"enabled": [], "total": 0}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def start_api_server(state, host: str = "127.0.0.1", port: int = 8765):
|
|
188
|
+
"""Start the API server in a background thread.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
state: WebTapState instance from the main app.
|
|
192
|
+
host: Host to bind to. Defaults to 127.0.0.1.
|
|
193
|
+
port: Port to bind to. Defaults to 8765.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Thread instance running the server.
|
|
197
|
+
"""
|
|
198
|
+
global app_state
|
|
199
|
+
app_state = state
|
|
200
|
+
|
|
201
|
+
thread = threading.Thread(target=run_server, args=(host, port), daemon=True)
|
|
202
|
+
thread.start()
|
|
203
|
+
|
|
204
|
+
logger.info(f"API server started on http://{host}:{port}")
|
|
205
|
+
return thread
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def run_server(host: str, port: int):
|
|
209
|
+
"""Run the FastAPI server in a thread."""
|
|
210
|
+
try:
|
|
211
|
+
uvicorn.run(
|
|
212
|
+
api,
|
|
213
|
+
host=host,
|
|
214
|
+
port=port,
|
|
215
|
+
log_level="error",
|
|
216
|
+
access_log=False,
|
|
217
|
+
)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"API server failed: {e}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = ["start_api_server"]
|
webtap/app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Main application entry point for WebTap browser debugger.
|
|
2
|
+
|
|
3
|
+
PUBLIC API:
|
|
4
|
+
- WebTapState: Application state class with CDP session and service
|
|
5
|
+
- app: Main ReplKit2 App instance (imported by commands and __init__)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from replkit2 import App
|
|
12
|
+
|
|
13
|
+
from webtap.cdp import CDPSession
|
|
14
|
+
from webtap.services import WebTapService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class WebTapState:
|
|
19
|
+
"""Application state for WebTap browser debugging.
|
|
20
|
+
|
|
21
|
+
Maintains CDP session and connection state for browser interaction.
|
|
22
|
+
All data is stored in DuckDB via the CDP session - no caching needed.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
cdp: Chrome DevTools Protocol session instance.
|
|
26
|
+
service: WebTapService orchestrating all domain services.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
cdp: CDPSession = field(default_factory=CDPSession)
|
|
30
|
+
service: WebTapService = field(init=False)
|
|
31
|
+
|
|
32
|
+
def __post_init__(self):
|
|
33
|
+
"""Initialize service with self reference after dataclass init."""
|
|
34
|
+
self.service = WebTapService(self)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Must be created before command imports for decorator registration
|
|
38
|
+
app = App(
|
|
39
|
+
"webtap",
|
|
40
|
+
WebTapState,
|
|
41
|
+
uri_scheme="webtap",
|
|
42
|
+
fastmcp={
|
|
43
|
+
"description": "Chrome DevTools Protocol debugger",
|
|
44
|
+
"tags": {"browser", "debugging", "chrome", "cdp"},
|
|
45
|
+
},
|
|
46
|
+
typer_config={
|
|
47
|
+
"add_completion": False, # Hide shell completion options
|
|
48
|
+
"help": "WebTap - Chrome DevTools Protocol CLI",
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Command imports trigger @app.command decorator registration
|
|
53
|
+
if "--cli" in sys.argv:
|
|
54
|
+
# Only import CLI-compatible commands (no dict/list parameters)
|
|
55
|
+
from webtap.commands import setup # noqa: E402, F401
|
|
56
|
+
from webtap.commands import launch # noqa: E402, F401
|
|
57
|
+
else:
|
|
58
|
+
# Import all commands for REPL/MCP mode
|
|
59
|
+
from webtap.commands import connection # noqa: E402, F401
|
|
60
|
+
from webtap.commands import navigation # noqa: E402, F401
|
|
61
|
+
from webtap.commands import javascript # noqa: E402, F401
|
|
62
|
+
from webtap.commands import network # noqa: E402, F401
|
|
63
|
+
from webtap.commands import console # noqa: E402, F401
|
|
64
|
+
from webtap.commands import events # noqa: E402, F401
|
|
65
|
+
from webtap.commands import filters # noqa: E402, F401
|
|
66
|
+
from webtap.commands import inspect # noqa: E402, F401
|
|
67
|
+
from webtap.commands import fetch # noqa: E402, F401
|
|
68
|
+
from webtap.commands import body # noqa: E402, F401
|
|
69
|
+
from webtap.commands import setup # noqa: E402, F401
|
|
70
|
+
from webtap.commands import launch # noqa: E402, F401
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Entry point is in __init__.py:main() as specified in pyproject.toml
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = ["WebTapState", "app"]
|