webtap-tool 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webtap-tool might be problematic. Click here for more details.
- webtap/api.py +318 -9
- webtap/app.py +15 -9
- webtap/cdp/session.py +101 -1
- webtap/commands/DEVELOPER_GUIDE.md +108 -22
- webtap/commands/TIPS.md +24 -1
- webtap/commands/_builders.py +139 -1
- webtap/commands/body.py +1 -2
- webtap/commands/connection.py +1 -2
- webtap/commands/console.py +1 -2
- webtap/commands/events.py +1 -2
- webtap/commands/fetch.py +1 -2
- webtap/commands/filters.py +95 -62
- webtap/commands/inspect.py +1 -2
- webtap/commands/javascript.py +41 -26
- webtap/commands/navigation.py +1 -2
- webtap/commands/network.py +11 -7
- webtap/commands/selections.py +129 -0
- webtap/commands/server.py +19 -0
- webtap/filters.py +116 -56
- webtap/services/dom.py +512 -0
- webtap/services/main.py +14 -0
- {webtap_tool-0.3.0.dist-info → webtap_tool-0.5.0.dist-info}/METADATA +2 -2
- {webtap_tool-0.3.0.dist-info → webtap_tool-0.5.0.dist-info}/RECORD +25 -24
- webtap/commands/_errors.py +0 -108
- {webtap_tool-0.3.0.dist-info → webtap_tool-0.5.0.dist-info}/WHEEL +0 -0
- {webtap_tool-0.3.0.dist-info → webtap_tool-0.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -173,7 +173,7 @@ def command(state, param: int = 0)
|
|
|
173
173
|
@app.command(display="markdown", fastmcp={"type": "resource", "mime_type": "text/markdown"})
|
|
174
174
|
def pages(state) -> dict:
|
|
175
175
|
"""List available pages."""
|
|
176
|
-
return
|
|
176
|
+
return table_response(
|
|
177
177
|
title="Chrome Pages",
|
|
178
178
|
headers=["Index", "Title", "URL"],
|
|
179
179
|
rows=rows,
|
|
@@ -187,7 +187,7 @@ def pages(state) -> dict:
|
|
|
187
187
|
def navigate(state, url: str) -> dict:
|
|
188
188
|
"""Navigate to URL."""
|
|
189
189
|
# Perform action
|
|
190
|
-
return
|
|
190
|
+
return info_response(
|
|
191
191
|
title="Navigation Complete",
|
|
192
192
|
fields={"URL": url, "Status": "Success"}
|
|
193
193
|
)
|
|
@@ -195,37 +195,75 @@ def navigate(state, url: str) -> dict:
|
|
|
195
195
|
|
|
196
196
|
## Error Handling
|
|
197
197
|
|
|
198
|
-
Always use the error utilities from `
|
|
198
|
+
Always use the error utilities from `_builders.py`:
|
|
199
199
|
|
|
200
200
|
```python
|
|
201
|
-
from webtap.commands.
|
|
201
|
+
from webtap.commands._builders import check_connection, error_response
|
|
202
202
|
|
|
203
203
|
def my_command(state, ...):
|
|
204
204
|
# Check connection first for commands that need it
|
|
205
205
|
if error := check_connection(state):
|
|
206
206
|
return error
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
# Validate parameters
|
|
209
209
|
if not valid:
|
|
210
|
-
return error_response("
|
|
211
|
-
|
|
212
|
-
#
|
|
213
|
-
return error_response("
|
|
210
|
+
return error_response("Parameter X must be Y", suggestions=["Try this", "Or that"])
|
|
211
|
+
|
|
212
|
+
# Simple errors
|
|
213
|
+
return error_response("Specific error message")
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
##
|
|
216
|
+
## Response Builders
|
|
217
217
|
|
|
218
|
-
Use
|
|
218
|
+
Use builders from `_builders.py`:
|
|
219
219
|
|
|
220
220
|
```python
|
|
221
|
-
from webtap.commands.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
from webtap.commands._builders import (
|
|
222
|
+
# Table responses
|
|
223
|
+
table_response, # Tables with headers, warnings, tips
|
|
224
|
+
|
|
225
|
+
# Info displays
|
|
226
|
+
info_response, # Key-value pairs with optional heading
|
|
227
|
+
|
|
228
|
+
# Status responses
|
|
229
|
+
error_response, # Errors with suggestions
|
|
230
|
+
success_response, # Success messages with details
|
|
231
|
+
warning_response, # Warnings with suggestions
|
|
232
|
+
|
|
233
|
+
# Code results
|
|
234
|
+
code_result_response, # Code execution with result display
|
|
235
|
+
code_response, # Simple code block display
|
|
236
|
+
|
|
237
|
+
# Connection helpers
|
|
238
|
+
check_connection, # Helper for CDP connection validation
|
|
239
|
+
check_fetch_enabled, # Helper for fetch interception validation
|
|
240
|
+
)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Usage Examples
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
# Table with tips
|
|
247
|
+
return table_response(
|
|
248
|
+
title="Network Requests",
|
|
249
|
+
headers=["ID", "URL", "Status"],
|
|
250
|
+
rows=rows,
|
|
251
|
+
summary=f"{len(rows)} requests",
|
|
252
|
+
tips=["Use body(ID) to fetch response body"]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Code execution result
|
|
256
|
+
return code_result_response(
|
|
257
|
+
"JavaScript Result",
|
|
258
|
+
code="2 + 2",
|
|
259
|
+
language="javascript",
|
|
260
|
+
result=4
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Error with suggestions
|
|
264
|
+
return error_response(
|
|
265
|
+
"Not connected to Chrome",
|
|
266
|
+
suggestions=["Run pages()", "Use connect(0)"]
|
|
229
267
|
)
|
|
230
268
|
```
|
|
231
269
|
|
|
@@ -300,15 +338,63 @@ Use explicit text instead of symbols for clarity:
|
|
|
300
338
|
4. **MCP mode**: Test with `webtap --mcp` command
|
|
301
339
|
5. **Markdown rendering**: Verify output displays correctly
|
|
302
340
|
|
|
341
|
+
## TIPS.md Integration
|
|
342
|
+
|
|
343
|
+
All commands should be documented in `TIPS.md` for consistent help and MCP descriptions:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from webtap.commands._tips import get_tips, get_mcp_description
|
|
347
|
+
|
|
348
|
+
# Get MCP description (for fastmcp metadata)
|
|
349
|
+
mcp_desc = get_mcp_description("mycommand")
|
|
350
|
+
|
|
351
|
+
@app.command(
|
|
352
|
+
display="markdown",
|
|
353
|
+
fastmcp={"type": "tool", "description": mcp_desc} if mcp_desc else {"type": "tool"}
|
|
354
|
+
)
|
|
355
|
+
def mycommand(state, param: str) -> dict:
|
|
356
|
+
"""My command description."""
|
|
357
|
+
# ... implementation ...
|
|
358
|
+
|
|
359
|
+
# Include tips in response
|
|
360
|
+
tips = get_tips("mycommand", context={"id": some_id})
|
|
361
|
+
return table_response(
|
|
362
|
+
headers=headers,
|
|
363
|
+
rows=rows,
|
|
364
|
+
tips=tips # Automatically shown as "Next Steps"
|
|
365
|
+
)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Adding to TIPS.md
|
|
369
|
+
|
|
370
|
+
Add a section for your command:
|
|
371
|
+
|
|
372
|
+
```markdown
|
|
373
|
+
### mycommand
|
|
374
|
+
Brief description of what the command does.
|
|
375
|
+
|
|
376
|
+
#### Examples
|
|
377
|
+
\```python
|
|
378
|
+
mycommand("param1") # Basic usage
|
|
379
|
+
mycommand("param2", flag=True) # With options
|
|
380
|
+
\```
|
|
381
|
+
|
|
382
|
+
#### Tips
|
|
383
|
+
- **Related command:** `other_command()` - does related thing
|
|
384
|
+
- **Advanced usage:** Use with `yet_another()` for X
|
|
385
|
+
- **Context aware:** Tips support {id} placeholders from context dict
|
|
386
|
+
```
|
|
387
|
+
|
|
303
388
|
## Checklist for New Commands
|
|
304
389
|
|
|
305
390
|
- [ ] Use `@app.command()` decorator with `display="markdown"`
|
|
306
391
|
- [ ] Add `fastmcp` metadata (type: "resource" or "tool")
|
|
307
392
|
- [ ] Use simple types only (no unions, no Optional)
|
|
308
393
|
- [ ] Add `# pyright: ignore[reportArgumentType]` for `dict = None`
|
|
309
|
-
- [ ] Import
|
|
310
|
-
- [ ] Use `
|
|
394
|
+
- [ ] Import builders from `_builders.py`
|
|
395
|
+
- [ ] Use `table_response()`, `info_response()`, or `code_result_response()`
|
|
311
396
|
- [ ] Check connection with `check_connection()` if needed
|
|
397
|
+
- [ ] Add command section to `TIPS.md` with examples and tips
|
|
398
|
+
- [ ] Use `get_tips()` to show tips in response
|
|
312
399
|
- [ ] Document parameters clearly in docstring
|
|
313
|
-
- [ ] Provide usage examples in docstring
|
|
314
400
|
- [ ] Test in both REPL and MCP modes
|
webtap/commands/TIPS.md
CHANGED
|
@@ -150,4 +150,27 @@ Show paused requests and responses.
|
|
|
150
150
|
- **Analyze body:** `body({id})` - fetch and examine response body
|
|
151
151
|
- **Resume request:** `resume({id})` - continue the request
|
|
152
152
|
- **Modify request:** `resume({id}, modifications={'url': '...'})`
|
|
153
|
-
- **Fail request:** `fail({id}, 'BlockedByClient')` - block the request
|
|
153
|
+
- **Fail request:** `fail({id}, 'BlockedByClient')` - block the request
|
|
154
|
+
|
|
155
|
+
### selections
|
|
156
|
+
Browser element selections with prompt and analysis.
|
|
157
|
+
|
|
158
|
+
Access selected DOM elements and their properties via Python expressions. Elements are selected using the Chrome extension's selection mode.
|
|
159
|
+
|
|
160
|
+
#### Examples
|
|
161
|
+
```python
|
|
162
|
+
selections() # View all selections
|
|
163
|
+
selections(expr="data['prompt']") # Get prompt text
|
|
164
|
+
selections(expr="data['selections']['1']") # Get element #1 data
|
|
165
|
+
selections(expr="data['selections']['1']['styles']") # Get styles
|
|
166
|
+
selections(expr="len(data['selections'])") # Count selections
|
|
167
|
+
selections(expr="{k: v['selector'] for k, v in data['selections'].items()}") # All selectors
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Tips
|
|
171
|
+
- **Extract HTML:** `selections(expr="data['selections']['1']['outerHTML']")` - get element HTML
|
|
172
|
+
- **Get CSS selector:** `selections(expr="data['selections']['1']['selector']")` - unique selector
|
|
173
|
+
- **Use with js():** `js("element.offsetWidth", selection=1)` - integrate with JavaScript execution
|
|
174
|
+
- **Access styles:** `selections(expr="data['selections']['1']['styles']['display']")` - computed CSS
|
|
175
|
+
- **Get attributes:** `selections(expr="data['selections']['1']['preview']")` - tag, id, classes
|
|
176
|
+
- **Inspect in prompts:** Use `@webtap:webtap://selections` resource in Claude Code for AI analysis
|
webtap/commands/_builders.py
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
-
"""Response builders using ReplKit2 v0.10.0+ markdown elements.
|
|
1
|
+
"""Response builders using ReplKit2 v0.10.0+ markdown elements.
|
|
2
|
+
|
|
3
|
+
USAGE GUIDELINES:
|
|
4
|
+
|
|
5
|
+
Use builders for:
|
|
6
|
+
✅ Simple responses (error, info, success, warning)
|
|
7
|
+
✅ Tables with standard format
|
|
8
|
+
✅ Code execution results
|
|
9
|
+
✅ Repeated patterns across commands
|
|
10
|
+
|
|
11
|
+
Use manual building for:
|
|
12
|
+
❌ Complex multi-section layouts (>20 lines)
|
|
13
|
+
❌ Conditional sections with deep nesting
|
|
14
|
+
❌ Custom workflows (wizards, dashboards)
|
|
15
|
+
❌ Dual-mode resource views with tutorials
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
- network() - Simple table → Use table_response()
|
|
19
|
+
- javascript() - Code result → Use code_result_response()
|
|
20
|
+
- server() - Custom dashboard → Manual OK
|
|
21
|
+
- selections() resource mode - Tutorial layout → Manual OK
|
|
22
|
+
|
|
23
|
+
Available builders:
|
|
24
|
+
- table_response() - Tables with headers, warnings, tips
|
|
25
|
+
- info_response() - Key-value pairs with optional heading
|
|
26
|
+
- error_response() - Errors with suggestions
|
|
27
|
+
- success_response() - Success messages with details
|
|
28
|
+
- warning_response() - Warnings with suggestions
|
|
29
|
+
- check_connection() - Helper for CDP connection validation
|
|
30
|
+
- check_fetch_enabled() - Helper for fetch interception validation
|
|
31
|
+
- code_result_response() - Code execution with result display
|
|
32
|
+
- code_response() - Simple code block display
|
|
33
|
+
"""
|
|
2
34
|
|
|
3
35
|
from typing import Any
|
|
4
36
|
|
|
@@ -125,3 +157,109 @@ def warning_response(message: str, suggestions: list[str] | None = None) -> dict
|
|
|
125
157
|
elements.append({"type": "list", "items": suggestions})
|
|
126
158
|
|
|
127
159
|
return {"elements": elements}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Connection helpers
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def check_connection(state):
|
|
166
|
+
"""Check CDP connection, return error response if not connected.
|
|
167
|
+
|
|
168
|
+
Returns error_response() if not connected, None otherwise.
|
|
169
|
+
Use pattern: `if error := check_connection(state): return error`
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
state: Application state containing CDP session.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Error response dict if not connected, None if connected.
|
|
176
|
+
"""
|
|
177
|
+
if not (state.cdp and state.cdp.is_connected):
|
|
178
|
+
return error_response(
|
|
179
|
+
"Not connected to Chrome",
|
|
180
|
+
suggestions=[
|
|
181
|
+
"Run `pages()` to see available tabs",
|
|
182
|
+
"Use `connect(0)` to connect to first tab",
|
|
183
|
+
"Or `connect(page_id='...')` for specific tab",
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def check_fetch_enabled(state):
|
|
190
|
+
"""Check fetch interception, return error response if not enabled.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
state: Application state containing fetch service.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Error response dict if not enabled, None if enabled.
|
|
197
|
+
"""
|
|
198
|
+
if not state.service.fetch.enabled:
|
|
199
|
+
return error_response(
|
|
200
|
+
"Fetch interception not enabled", suggestions=["Enable with `fetch('enable')` to pause requests"]
|
|
201
|
+
)
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Code result builders
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def code_result_response(
|
|
209
|
+
title: str,
|
|
210
|
+
code: str,
|
|
211
|
+
language: str,
|
|
212
|
+
result: Any = None,
|
|
213
|
+
) -> dict:
|
|
214
|
+
"""Build code execution result display.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
title: Result heading (e.g. "JavaScript Result")
|
|
218
|
+
code: Source code executed
|
|
219
|
+
language: Syntax highlighting language
|
|
220
|
+
result: Execution result (supports dict/list/str/None)
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Markdown response with code and result
|
|
224
|
+
"""
|
|
225
|
+
import json
|
|
226
|
+
|
|
227
|
+
elements = [
|
|
228
|
+
{"type": "heading", "content": title, "level": 2},
|
|
229
|
+
{"type": "code_block", "content": code, "language": language},
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
if result is not None:
|
|
233
|
+
if isinstance(result, (dict, list)):
|
|
234
|
+
elements.append({"type": "code_block", "content": json.dumps(result, indent=2), "language": "json"})
|
|
235
|
+
else:
|
|
236
|
+
elements.append({"type": "text", "content": f"**Result:** `{result}`"})
|
|
237
|
+
else:
|
|
238
|
+
elements.append({"type": "text", "content": "**Result:** _(no return value)_"})
|
|
239
|
+
|
|
240
|
+
return {"elements": elements}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def code_response(
|
|
244
|
+
content: str,
|
|
245
|
+
language: str = "",
|
|
246
|
+
title: str | None = None,
|
|
247
|
+
) -> dict:
|
|
248
|
+
"""Build simple code block response.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
content: Code content to display
|
|
252
|
+
language: Syntax highlighting language
|
|
253
|
+
title: Optional heading above code block
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Markdown response with code block
|
|
257
|
+
"""
|
|
258
|
+
elements = []
|
|
259
|
+
|
|
260
|
+
if title:
|
|
261
|
+
elements.append({"type": "heading", "content": title, "level": 2})
|
|
262
|
+
|
|
263
|
+
elements.append({"type": "code_block", "content": content, "language": language})
|
|
264
|
+
|
|
265
|
+
return {"elements": elements}
|
webtap/commands/body.py
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from webtap.app import app
|
|
5
5
|
from webtap.commands._utils import evaluate_expression, format_expression_result
|
|
6
|
-
from webtap.commands.
|
|
7
|
-
from webtap.commands._builders import info_response, error_response
|
|
6
|
+
from webtap.commands._builders import check_connection, info_response, error_response
|
|
8
7
|
from webtap.commands._tips import get_mcp_description
|
|
9
8
|
|
|
10
9
|
|
webtap/commands/connection.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Chrome browser connection management commands."""
|
|
2
2
|
|
|
3
3
|
from webtap.app import app
|
|
4
|
-
from webtap.commands.
|
|
5
|
-
from webtap.commands._builders import info_response, table_response, error_response
|
|
4
|
+
from webtap.commands._builders import check_connection, info_response, table_response, error_response
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
@app.command(display="markdown", fastmcp={"type": "tool"})
|
webtap/commands/console.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Browser console message monitoring and display commands."""
|
|
2
2
|
|
|
3
3
|
from webtap.app import app
|
|
4
|
-
from webtap.commands._builders import table_response
|
|
5
|
-
from webtap.commands._errors import check_connection
|
|
4
|
+
from webtap.commands._builders import check_connection, table_response
|
|
6
5
|
from webtap.commands._tips import get_tips
|
|
7
6
|
|
|
8
7
|
|
webtap/commands/events.py
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from webtap.app import app
|
|
4
4
|
from webtap.cdp import build_query
|
|
5
|
-
from webtap.commands.
|
|
6
|
-
from webtap.commands._builders import table_response
|
|
5
|
+
from webtap.commands._builders import check_connection, table_response
|
|
7
6
|
from webtap.commands._tips import get_tips, get_mcp_description
|
|
8
7
|
|
|
9
8
|
|
webtap/commands/fetch.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""HTTP fetch request interception and debugging commands."""
|
|
2
2
|
|
|
3
3
|
from webtap.app import app
|
|
4
|
-
from webtap.commands.
|
|
5
|
-
from webtap.commands._builders import error_response, info_response, table_response
|
|
4
|
+
from webtap.commands._builders import check_connection, error_response, info_response, table_response
|
|
6
5
|
from webtap.commands._tips import get_tips
|
|
7
6
|
|
|
8
7
|
|
webtap/commands/filters.py
CHANGED
|
@@ -7,46 +7,19 @@ from webtap.commands._builders import info_response, error_response
|
|
|
7
7
|
@app.command(display="markdown", fastmcp={"type": "tool"})
|
|
8
8
|
def filters(state, action: str = "list", config: dict = None) -> dict: # pyright: ignore[reportArgumentType]
|
|
9
9
|
"""
|
|
10
|
-
Manage network request filters.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- "disable" - Disable category
|
|
24
|
-
- "save" - Save filters to disk
|
|
25
|
-
- "load" - Load filters from disk
|
|
26
|
-
config: Action-specific configuration
|
|
27
|
-
- For show: {"category": "ads"}
|
|
28
|
-
- For add: {"category": "ads", "patterns": ["*ad*"], "type": "domain"}
|
|
29
|
-
- For remove: {"patterns": ["*ad*"], "type": "domain"}
|
|
30
|
-
- For update: {"category": "ads", "domains": [...], "types": [...]}
|
|
31
|
-
- For delete/enable/disable: {"category": "ads"}
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
filters() # List all categories
|
|
35
|
-
filters("list") # Same as above
|
|
36
|
-
filters("show", {"category": "ads"}) # Show ads category details
|
|
37
|
-
filters("add", {"category": "ads",
|
|
38
|
-
"patterns": ["*doubleclick*"]}) # Add domain pattern
|
|
39
|
-
filters("add", {"category": "tracking",
|
|
40
|
-
"patterns": ["Ping"], "type": "type"}) # Add type pattern
|
|
41
|
-
filters("remove", {"patterns": ["*doubleclick*"]}) # Remove pattern
|
|
42
|
-
filters("update", {"category": "ads",
|
|
43
|
-
"domains": ["*google*", "*facebook*"]}) # Replace patterns
|
|
44
|
-
filters("delete", {"category": "ads"}) # Delete category
|
|
45
|
-
filters("save") # Persist to disk
|
|
46
|
-
filters("load") # Load from disk
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
Current filter configuration or operation result
|
|
10
|
+
Manage network request filters with include/exclude modes.
|
|
11
|
+
|
|
12
|
+
CDP Types: Document, XHR, Fetch, Image, Stylesheet, Script, Font, Media, Other, Ping
|
|
13
|
+
Domains filter URLs, types filter Chrome's resource loading mechanism.
|
|
14
|
+
|
|
15
|
+
Actions:
|
|
16
|
+
list: Show all categories (default)
|
|
17
|
+
show: Display category - config: {"category": "api"}
|
|
18
|
+
add: Add patterns - config: {"category": "api", "patterns": ["*api*"], "type": "domain", "mode": "include"}
|
|
19
|
+
remove: Remove patterns - config: {"patterns": ["*api*"], "type": "domain"}
|
|
20
|
+
update: Replace category - config: {"category": "api", "mode": "include", "domains": [...], "types": ["XHR", "Fetch"]}
|
|
21
|
+
delete/enable/disable: Manage category - config: {"category": "api"}
|
|
22
|
+
save/load: Persist to/from disk
|
|
50
23
|
"""
|
|
51
24
|
fm = state.service.filters
|
|
52
25
|
cfg = config or {}
|
|
@@ -54,14 +27,35 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
54
27
|
# Handle load operation
|
|
55
28
|
if action == "load":
|
|
56
29
|
if fm.load():
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
]
|
|
64
|
-
|
|
30
|
+
# Build table data
|
|
31
|
+
categories = fm.get_categories_summary()
|
|
32
|
+
rows = []
|
|
33
|
+
for cat in categories:
|
|
34
|
+
mode = cat["mode"] or "exclude"
|
|
35
|
+
mode_display = "include" if mode == "include" else "exclude"
|
|
36
|
+
if cat["mode"] is None:
|
|
37
|
+
mode_display = "exclude*" # Asterisk indicates missing mode field
|
|
38
|
+
|
|
39
|
+
rows.append(
|
|
40
|
+
{
|
|
41
|
+
"Category": cat["name"],
|
|
42
|
+
"Status": "enabled" if cat["enabled"] else "disabled",
|
|
43
|
+
"Mode": mode_display,
|
|
44
|
+
"Domains": str(cat["domain_count"]),
|
|
45
|
+
"Types": str(cat["type_count"]),
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
elements = [
|
|
50
|
+
{"type": "heading", "content": "Filters Loaded", "level": 2},
|
|
51
|
+
{"type": "text", "content": f"From: `{fm.filter_path}`"},
|
|
52
|
+
{"type": "table", "headers": ["Category", "Status", "Mode", "Domains", "Types"], "rows": rows},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
if any(cat["mode"] is None for cat in categories):
|
|
56
|
+
elements.append({"type": "text", "content": "_* Mode not specified, defaulting to exclude_"})
|
|
57
|
+
|
|
58
|
+
return {"elements": elements}
|
|
65
59
|
else:
|
|
66
60
|
return error_response(f"No filters found at {fm.filter_path}")
|
|
67
61
|
|
|
@@ -82,6 +76,7 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
82
76
|
category = cfg.get("category", "custom")
|
|
83
77
|
patterns = cfg.get("patterns", [])
|
|
84
78
|
pattern_type = cfg.get("type", "domain")
|
|
79
|
+
mode = cfg.get("mode") # Required, no default
|
|
85
80
|
|
|
86
81
|
if not patterns:
|
|
87
82
|
# Legacy single pattern support
|
|
@@ -94,11 +89,14 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
94
89
|
|
|
95
90
|
added = []
|
|
96
91
|
failed = []
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
try:
|
|
93
|
+
for pattern in patterns:
|
|
94
|
+
if fm.add_pattern(pattern, category, pattern_type, mode):
|
|
95
|
+
added.append(pattern)
|
|
96
|
+
else:
|
|
97
|
+
failed.append(pattern)
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
return error_response(str(e))
|
|
102
100
|
|
|
103
101
|
if added and not failed:
|
|
104
102
|
return info_response(
|
|
@@ -107,6 +105,7 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
107
105
|
"Type": "Domain pattern" if pattern_type == "domain" else "Resource type",
|
|
108
106
|
"Patterns": ", ".join(added),
|
|
109
107
|
"Category": category,
|
|
108
|
+
"Mode": mode,
|
|
110
109
|
},
|
|
111
110
|
)
|
|
112
111
|
elif failed:
|
|
@@ -149,8 +148,10 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
149
148
|
return error_response("'category' required for update action")
|
|
150
149
|
|
|
151
150
|
category = cfg["category"]
|
|
152
|
-
fm.update_category(category, domains=cfg.get("domains"), types=cfg.get("types"))
|
|
153
|
-
return info_response(
|
|
151
|
+
fm.update_category(category, domains=cfg.get("domains"), types=cfg.get("types"), mode=cfg.get("mode"))
|
|
152
|
+
return info_response(
|
|
153
|
+
title="Category Updated", fields={"Category": category, "Mode": cfg.get("mode", "exclude")}
|
|
154
|
+
)
|
|
154
155
|
|
|
155
156
|
# Handle delete operation
|
|
156
157
|
elif action == "delete":
|
|
@@ -193,10 +194,12 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
193
194
|
if category in fm.filters:
|
|
194
195
|
filters = fm.filters[category]
|
|
195
196
|
enabled = "Enabled" if category in fm.enabled_categories else "Disabled"
|
|
197
|
+
mode = filters.get("mode", "exclude")
|
|
196
198
|
|
|
197
199
|
elements = [
|
|
198
200
|
{"type": "heading", "content": f"Category: {category}", "level": 2},
|
|
199
201
|
{"type": "text", "content": f"**Status:** {enabled}"},
|
|
202
|
+
{"type": "text", "content": f"**Mode:** {mode}"},
|
|
200
203
|
]
|
|
201
204
|
|
|
202
205
|
if filters.get("domains"):
|
|
@@ -212,13 +215,43 @@ def filters(state, action: str = "list", config: dict = None) -> dict: # pyrigh
|
|
|
212
215
|
|
|
213
216
|
# Default list action: show all filters
|
|
214
217
|
elif action == "list" or action == "":
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
if not fm.filters:
|
|
219
|
+
return {
|
|
220
|
+
"elements": [
|
|
221
|
+
{"type": "heading", "content": "Filter Configuration", "level": 2},
|
|
222
|
+
{"type": "text", "content": f"No filters loaded (would load from `{fm.filter_path}`)"},
|
|
223
|
+
{"type": "text", "content": "Use `filters('load')` to load filters from disk"},
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Build table data
|
|
228
|
+
categories = fm.get_categories_summary()
|
|
229
|
+
rows = []
|
|
230
|
+
for cat in categories:
|
|
231
|
+
mode = cat["mode"] or "exclude"
|
|
232
|
+
mode_display = "include" if mode == "include" else "exclude"
|
|
233
|
+
if cat["mode"] is None:
|
|
234
|
+
mode_display = "exclude*"
|
|
235
|
+
|
|
236
|
+
rows.append(
|
|
237
|
+
{
|
|
238
|
+
"Category": cat["name"],
|
|
239
|
+
"Status": "enabled" if cat["enabled"] else "disabled",
|
|
240
|
+
"Mode": mode_display,
|
|
241
|
+
"Domains": str(cat["domain_count"]),
|
|
242
|
+
"Types": str(cat["type_count"]),
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
elements = [
|
|
247
|
+
{"type": "heading", "content": "Filter Configuration", "level": 2},
|
|
248
|
+
{"type": "table", "headers": ["Category", "Status", "Mode", "Domains", "Types"], "rows": rows},
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
if any(cat["mode"] is None for cat in categories):
|
|
252
|
+
elements.append({"type": "text", "content": "_* Mode not specified, defaulting to exclude_"})
|
|
253
|
+
|
|
254
|
+
return {"elements": elements}
|
|
222
255
|
|
|
223
256
|
else:
|
|
224
257
|
return error_response(f"Unknown action: {action}")
|
webtap/commands/inspect.py
CHANGED
|
@@ -4,8 +4,7 @@ import json
|
|
|
4
4
|
|
|
5
5
|
from webtap.app import app
|
|
6
6
|
from webtap.commands._utils import evaluate_expression, format_expression_result
|
|
7
|
-
from webtap.commands.
|
|
8
|
-
from webtap.commands._builders import error_response
|
|
7
|
+
from webtap.commands._builders import check_connection, error_response
|
|
9
8
|
from webtap.commands._tips import get_mcp_description
|
|
10
9
|
|
|
11
10
|
|