webtap-tool 0.5.2__tar.gz → 0.6.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.

Potentially problematic release.


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

Files changed (59) hide show
  1. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/CHANGELOG.md +16 -0
  2. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/PKG-INFO +2 -1
  3. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/pyproject.toml +2 -1
  4. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/app.py +1 -0
  5. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/TIPS.md +19 -0
  6. webtap_tool-0.6.0/src/webtap/commands/to_model.py +129 -0
  7. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/.gitignore +0 -0
  8. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/ARCHITECTURE.md +0 -0
  9. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/README.md +0 -0
  10. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/data/filters.json +0 -0
  11. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/extension/content.js +0 -0
  12. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/extension/manifest.json +0 -0
  13. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/extension/sidepanel.html +0 -0
  14. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/extension/sidepanel.js +0 -0
  15. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/llms.txt +0 -0
  16. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/VISION.md +0 -0
  17. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/__init__.py +0 -0
  18. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/api.py +0 -0
  19. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/README.md +0 -0
  20. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/__init__.py +0 -0
  21. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/query.py +0 -0
  22. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/schema/README.md +0 -0
  23. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/schema/cdp_protocol.json +0 -0
  24. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/schema/cdp_version.json +0 -0
  25. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/cdp/session.py +0 -0
  26. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/DEVELOPER_GUIDE.md +0 -0
  27. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/__init__.py +0 -0
  28. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/_builders.py +0 -0
  29. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/_tips.py +0 -0
  30. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/_utils.py +0 -0
  31. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/body.py +0 -0
  32. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/connection.py +0 -0
  33. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/console.py +0 -0
  34. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/events.py +0 -0
  35. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/fetch.py +0 -0
  36. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/filters.py +0 -0
  37. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/inspect.py +0 -0
  38. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/javascript.py +0 -0
  39. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/launch.py +0 -0
  40. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/navigation.py +0 -0
  41. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/network.py +0 -0
  42. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/selections.py +0 -0
  43. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/server.py +0 -0
  44. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/commands/setup.py +0 -0
  45. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/filters.py +0 -0
  46. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/README.md +0 -0
  47. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/__init__.py +0 -0
  48. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/body.py +0 -0
  49. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/console.py +0 -0
  50. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/dom.py +0 -0
  51. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/fetch.py +0 -0
  52. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/main.py +0 -0
  53. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/network.py +0 -0
  54. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/__init__.py +0 -0
  55. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/chrome.py +0 -0
  56. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/desktop.py +0 -0
  57. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/extension.py +0 -0
  58. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/filters.py +0 -0
  59. {webtap_tool-0.5.2 → webtap_tool-0.6.0}/src/webtap/services/setup/platform.py +0 -0
@@ -15,6 +15,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
 
16
16
  ### Removed
17
17
 
18
+ ## [0.6.0] - 2025-10-10
19
+
20
+ ### Added
21
+ - **to_model command**: Generate Pydantic v2 models from JSON response bodies using datamodel-codegen
22
+ - Supports `json_path` parameter for extracting nested objects (e.g., `"Data[0]"`)
23
+ - Auto-applies snake_case fields and modern type hints (`list`, `dict`, `| None`)
24
+ - Perfect for reverse engineering APIs - inspect with `body()`, generate models with `to_model()`
25
+ - **datamodel-code-generator dependency**: Added for Python-native model generation (no subprocess)
26
+
27
+ ### Changed
28
+ - **TIPS.md**: Added `to_model` cross-references in `body` and `network` tips
29
+
30
+ ### Fixed
31
+
32
+ ### Removed
33
+
18
34
  ## [0.5.2] - 2025-10-09
19
35
 
20
36
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webtap-tool
3
- Version: 0.5.2
3
+ Version: 0.6.0
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
@@ -12,6 +12,7 @@ Classifier: Topic :: Software Development :: Debuggers
12
12
  Requires-Python: >=3.12
13
13
  Requires-Dist: beautifulsoup4>=4.13.5
14
14
  Requires-Dist: cryptography>=45.0.6
15
+ Requires-Dist: datamodel-code-generator>=0.35.0
15
16
  Requires-Dist: duckdb>=1.3.2
16
17
  Requires-Dist: fastapi>=0.116.1
17
18
  Requires-Dist: httpx>=0.28.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webtap-tool"
3
- version = "0.5.2"
3
+ version = "0.6.0"
4
4
  description = "Terminal-based web page inspector for AI debugging sessions"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -18,6 +18,7 @@ classifiers = [
18
18
  dependencies = [
19
19
  "beautifulsoup4>=4.13.5",
20
20
  "cryptography>=45.0.6",
21
+ "datamodel-code-generator>=0.35.0",
21
22
  "duckdb>=1.3.2",
22
23
  "fastapi>=0.116.1",
23
24
  "httpx>=0.28.1",
@@ -89,6 +89,7 @@ else:
89
89
  from webtap.commands import inspect # noqa: E402, F401
90
90
  from webtap.commands import fetch # noqa: E402, F401
91
91
  from webtap.commands import body # noqa: E402, F401
92
+ from webtap.commands import to_model # noqa: E402, F401
92
93
  from webtap.commands import selections # noqa: E402, F401
93
94
  from webtap.commands import server # noqa: E402, F401
94
95
  from webtap.commands import setup # noqa: E402, F401
@@ -26,12 +26,30 @@ body(123, "msgpack.unpackb(body)") # Binary formats
26
26
  ```
27
27
 
28
28
  #### Tips
29
+ - **Generate models:** `to_model({id}, "models/model.py")` - create Pydantic models from JSON
29
30
  - **Chain requests:** `body({id}, "httpx.get(json.loads(body)['next_url']).text[:100]")`
30
31
  - **Parse XML:** `body({id}, "ElementTree.fromstring(body).find('.//title').text")`
31
32
  - **Extract forms:** `body({id}, "[f['action'] for f in bs4(body, 'html.parser').find_all('form')]")`
32
33
  - **Decode protobuf:** `body({id}, "protobuf_json.Parse(body, YourMessage())")`
33
34
  - **Find related:** `events({'requestId': request_id})` - related events
34
35
 
36
+ ### to_model
37
+ Generate Pydantic v2 models from JSON response bodies for reverse engineering APIs.
38
+
39
+ #### Examples
40
+ ```python
41
+ to_model(123, "models/product.py") # Generate from full response
42
+ to_model(123, "models/customer.py", json_path="Data[0]") # Extract nested object
43
+ to_model(123, "/tmp/model.py", json_path="items") # Extract array items
44
+ ```
45
+
46
+ #### Tips
47
+ - **Check structure first:** `body({id})` - preview JSON before generating
48
+ - **Extract nested data:** Use `json_path="Data[0]"` to extract specific objects
49
+ - **Array items:** Extract first item with `json_path="items[0]"` for model generation
50
+ - **Auto-cleanup:** Generated models use snake_case fields and modern type hints (list, dict, | None)
51
+ - **Edit after:** Add `Field(alias="...")` manually for API field mapping
52
+
35
53
  ### inspect
36
54
  Inspect CDP events with full Python debugging.
37
55
 
@@ -62,6 +80,7 @@ Show network requests with full data.
62
80
 
63
81
  #### Tips
64
82
  - **Analyze responses:** `body({id})` - fetch response body
83
+ - **Generate models:** `to_model({id}, "models/model.py")` - create Pydantic models from JSON
65
84
  - **Parse HTML:** `body({id}, "bs4(body, 'html.parser').find('title').text")`
66
85
  - **Extract JSON:** `body({id}, "json.loads(body)['data']")`
67
86
  - **Find patterns:** `body({id}, "re.findall(r'/api/\\w+', body)")`
@@ -0,0 +1,129 @@
1
+ """Generate Pydantic models from HTTP response bodies."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from datamodel_code_generator import generate, InputFileType, DataModelType
6
+ from webtap.app import app
7
+ from webtap.commands._builders import check_connection, success_response, error_response
8
+ from webtap.commands._tips import get_mcp_description
9
+
10
+
11
+ mcp_desc = get_mcp_description("to_model")
12
+
13
+
14
+ @app.command(display="markdown", fastmcp={"type": "tool", "description": mcp_desc} if mcp_desc else {"type": "tool"})
15
+ def to_model(state, response: int, output: str, json_path: str = None) -> dict: # pyright: ignore[reportArgumentType]
16
+ """Generate Pydantic model from response body using datamodel-codegen.
17
+
18
+ Args:
19
+ response: Response row ID from network() table
20
+ output: Output file path for generated model (e.g., "models/product.py")
21
+ json_path: Optional JSON path to extract nested data (e.g., "Data[0]")
22
+
23
+ Returns:
24
+ Success message with generation details
25
+ """
26
+ if error := check_connection(state):
27
+ return error
28
+
29
+ # Get response body from service
30
+ body_service = state.service.body
31
+ result = body_service.get_response_body(response, use_cache=True)
32
+
33
+ if "error" in result:
34
+ return error_response(result["error"])
35
+
36
+ body_content = result.get("body", "")
37
+ is_base64 = result.get("base64Encoded", False)
38
+
39
+ # Decode if needed
40
+ if is_base64:
41
+ decoded = body_service.decode_body(body_content, is_base64)
42
+ if isinstance(decoded, bytes):
43
+ return error_response(
44
+ "Response body is binary",
45
+ suggestions=["Only JSON responses can be converted to models", "Try a different response"],
46
+ )
47
+ body_content = decoded
48
+
49
+ # Parse JSON
50
+ try:
51
+ data = json.loads(body_content)
52
+ except json.JSONDecodeError as e:
53
+ return error_response(
54
+ f"Invalid JSON: {e}",
55
+ suggestions=[
56
+ "Response must be valid JSON",
57
+ "Check the response with body() first",
58
+ "Try a different response",
59
+ ],
60
+ )
61
+
62
+ # Extract JSON path if specified
63
+ if json_path:
64
+ try:
65
+ # Support simple bracket notation like "Data[0]"
66
+ parts = json_path.replace("[", ".").replace("]", "").split(".")
67
+ for part in parts:
68
+ if part:
69
+ if part.isdigit():
70
+ data = data[int(part)]
71
+ else:
72
+ data = data[part]
73
+ except (KeyError, IndexError, TypeError) as e:
74
+ return error_response(
75
+ f"JSON path extraction failed: {e}",
76
+ suggestions=[
77
+ f"Path '{json_path}' not found in response",
78
+ "Check the response structure with body()",
79
+ 'Try a simpler path like "Data" or "Data[0]"',
80
+ ],
81
+ )
82
+
83
+ # Ensure data is dict or list for model generation
84
+ if not isinstance(data, (dict, list)):
85
+ return error_response(
86
+ f"Extracted data is {type(data).__name__}, not dict or list",
87
+ suggestions=[
88
+ "Model generation requires dict or list structure",
89
+ "Adjust json_path to extract a complex object",
90
+ ],
91
+ )
92
+
93
+ # Create output directory if needed
94
+ output_path = Path(output).expanduser().resolve()
95
+ output_path.parent.mkdir(parents=True, exist_ok=True)
96
+
97
+ # Generate model using datamodel-codegen Python API
98
+ try:
99
+ generate(
100
+ json.dumps(data),
101
+ input_file_type=InputFileType.Json,
102
+ input_filename="response.json",
103
+ output=output_path,
104
+ output_model_type=DataModelType.PydanticV2BaseModel,
105
+ snake_case_field=True, # Convert to snake_case
106
+ use_standard_collections=True, # Use list instead of List
107
+ use_union_operator=True, # Use | instead of Union
108
+ )
109
+ except Exception as e:
110
+ return error_response(
111
+ f"Model generation failed: {e}",
112
+ suggestions=[
113
+ "Check that the JSON structure is valid",
114
+ "Try simplifying the JSON path",
115
+ "Ensure output directory is writable",
116
+ ],
117
+ )
118
+
119
+ # Count fields in generated model
120
+ try:
121
+ model_content = output_path.read_text()
122
+ field_count = model_content.count(": ") # Count field definitions
123
+ except Exception:
124
+ field_count = "unknown"
125
+
126
+ return success_response(
127
+ "Model generated successfully",
128
+ details={"Output": str(output_path), "Fields": field_count, "Size": f"{output_path.stat().st_size} bytes"},
129
+ )
File without changes
File without changes
File without changes
File without changes