webskrap 3.2.3__tar.gz → 3.2.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.
Files changed (104) hide show
  1. {webskrap-3.2.3 → webskrap-3.2.4}/CHANGELOG.md +23 -1
  2. {webskrap-3.2.3 → webskrap-3.2.4}/CONTRIBUTING.md +1 -1
  3. {webskrap-3.2.3 → webskrap-3.2.4}/PKG-INFO +2 -1
  4. {webskrap-3.2.3 → webskrap-3.2.4}/SECURITY.md +4 -4
  5. {webskrap-3.2.3 → webskrap-3.2.4}/docs/diagnostics/datadome.md +3 -3
  6. {webskrap-3.2.3 → webskrap-3.2.4}/pyproject.toml +16 -3
  7. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cli.py +2 -0
  8. {webskrap-3.2.3 → webskrap-3.2.4}/uv.lock +1 -1
  9. webskrap-3.2.3/.agents/skills/webskrap/SKILL.md +0 -151
  10. webskrap-3.2.3/.agents/skills/webskrap/references/cli.md +0 -154
  11. webskrap-3.2.3/.agents/skills/webskrap/references/mcp.md +0 -130
  12. webskrap-3.2.3/.agents/skills/webskrap/references/python-api.md +0 -147
  13. webskrap-3.2.3/.claude/skills/webskrap/SKILL.md +0 -1
  14. webskrap-3.2.3/.codex/skills/webskrap/SKILL.md +0 -1
  15. webskrap-3.2.3/tests/test_docs.py +0 -44
  16. {webskrap-3.2.3 → webskrap-3.2.4}/.gitattributes +0 -0
  17. {webskrap-3.2.3 → webskrap-3.2.4}/.gitignore +0 -0
  18. {webskrap-3.2.3/.package-dist.zjESut → webskrap-3.2.4/.package-dist.sya6Sl}/.gitignore +0 -0
  19. {webskrap-3.2.3 → webskrap-3.2.4}/LICENSE +0 -0
  20. {webskrap-3.2.3 → webskrap-3.2.4}/README.md +0 -0
  21. {webskrap-3.2.3 → webskrap-3.2.4}/benchmarks.py +0 -0
  22. {webskrap-3.2.3 → webskrap-3.2.4}/docs/design/cdp-driver.md +0 -0
  23. {webskrap-3.2.3 → webskrap-3.2.4}/docs/migration-3.0.md +0 -0
  24. {webskrap-3.2.3 → webskrap-3.2.4}/scripts/driver_parity.py +0 -0
  25. {webskrap-3.2.3 → webskrap-3.2.4}/scripts/live_stealth_report.py +0 -0
  26. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/__init__.py +0 -0
  27. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/browser_cli.py +0 -0
  28. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/browser_session.py +0 -0
  29. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cdp.py +0 -0
  30. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cdp_dom.py +0 -0
  31. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cdp_session.py +0 -0
  32. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cdp_setup.py +0 -0
  33. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/challenges.py +0 -0
  34. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cli.py +0 -0
  35. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/cli_output.py +0 -0
  36. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/client.py +0 -0
  37. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/consent.py +0 -0
  38. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/diagnostics.py +0 -0
  39. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/display.py +0 -0
  40. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/errors.py +0 -0
  41. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/fetch_runtime.py +0 -0
  42. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/guide.md +0 -0
  43. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/human.py +0 -0
  44. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/install.py +0 -0
  45. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/mcp_server.py +0 -0
  46. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/models.py +0 -0
  47. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/parsing.py +0 -0
  48. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/paths.py +0 -0
  49. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/profiles.py +0 -0
  50. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/proxy_auth.py +0 -0
  51. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/py.typed +0 -0
  52. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/search.py +0 -0
  53. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/text_search.py +0 -0
  54. {webskrap-3.2.3 → webskrap-3.2.4}/src/webskrap/urls.py +0 -0
  55. {webskrap-3.2.3 → webskrap-3.2.4}/tests/conftest.py +0 -0
  56. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/accepted-202.html +0 -0
  57. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/akamai-invalid-url-400.html +0 -0
  58. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/akamai-reference-403.html +0 -0
  59. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/akamai-sec-cpt.html +0 -0
  60. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/api-403.html +0 -0
  61. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/article-access-denied.html +0 -0
  62. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/cloudflare-1020.html +0 -0
  63. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/cloudflare-custom-block.html +0 -0
  64. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/blocks/not-found-404.html +0 -0
  65. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/bing.html +0 -0
  66. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/bing_challenge.html +0 -0
  67. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/bing_no_results.html +0 -0
  68. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/ddg.html +0 -0
  69. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/ddg_challenge.html +0 -0
  70. {webskrap-3.2.3 → webskrap-3.2.4}/tests/fixtures/search/ddg_no_results.html +0 -0
  71. {webskrap-3.2.3 → webskrap-3.2.4}/tests/live_stealth_helpers.py +0 -0
  72. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_bot_detection.py +0 -0
  73. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_bot_detection_headless.py +0 -0
  74. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_browser_cli.py +0 -0
  75. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_browser_integration.py +0 -0
  76. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_browser_session_unit.py +0 -0
  77. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_browser_vision.py +0 -0
  78. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cdp.py +0 -0
  79. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cdp_browser.py +0 -0
  80. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cdp_child_targets.py +0 -0
  81. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cdp_session.py +0 -0
  82. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_cdp_setup.py +0 -0
  83. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_challenges.py +0 -0
  84. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_client_unit.py +0 -0
  85. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_consent.py +0 -0
  86. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_consent_live.py +0 -0
  87. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_diagnostics.py +0 -0
  88. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_display.py +0 -0
  89. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_errors.py +0 -0
  90. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_fetch_runtime.py +0 -0
  91. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_human.py +0 -0
  92. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_install.py +0 -0
  93. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_live_stealth_helpers.py +0 -0
  94. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_live_stealth_report.py +0 -0
  95. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_mcp.py +0 -0
  96. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_mcp_efficiency.py +0 -0
  97. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_models.py +0 -0
  98. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_parsing.py +0 -0
  99. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_paths.py +0 -0
  100. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_profiles.py +0 -0
  101. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_search.py +0 -0
  102. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_search_live.py +0 -0
  103. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_text_search.py +0 -0
  104. {webskrap-3.2.3 → webskrap-3.2.4}/tests/test_urls.py +0 -0
@@ -9,6 +9,27 @@ history, so they summarize each release rather than list every change.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [3.2.4] - 2026-11-01
13
+
14
+ ### Added
15
+
16
+ - Python 3.15 is a supported version. CI runs the test suite on it, and the
17
+ package metadata lists it.
18
+
19
+ ### Changed
20
+
21
+ - The source distribution no longer contains the agent skill folders and the
22
+ test that checks them. They stay in the Git repository. Before, the sdist
23
+ held only one of the linked skill reference paths, and its test failed.
24
+
25
+ ### Fixed
26
+
27
+ - The CLI guide lists the exit status for each error code. It said before that
28
+ every browser command failure exits with 1.
29
+ - The MCP guide and the agent reference now describe `browser_wait_for`.
30
+ - `SECURITY.md` names the supported release line without an old version
31
+ number.
32
+
12
33
  ## [3.2.3] - 2026-10-30
13
34
 
14
35
  ### Security
@@ -980,7 +1001,8 @@ entries below.
980
1001
 
981
1002
  Earlier releases (0.1.0 - 0.4.9) are recorded in the git history and tags only.
982
1003
 
983
- [Unreleased]: https://github.com/kacigaya/webskrap/compare/v3.2.3...HEAD
1004
+ [Unreleased]: https://github.com/kacigaya/webskrap/compare/v3.2.4...HEAD
1005
+ [3.2.4]: https://github.com/kacigaya/webskrap/compare/v3.2.3...v3.2.4
984
1006
  [3.2.3]: https://github.com/kacigaya/webskrap/compare/v3.2.2...v3.2.3
985
1007
  [3.2.2]: https://github.com/kacigaya/webskrap/compare/v3.2.1...v3.2.2
986
1008
  [3.2.1]: https://github.com/kacigaya/webskrap/compare/v3.2.0...v3.2.1
@@ -5,7 +5,7 @@ what a reviewable pull request looks like.
5
5
 
6
6
  ## Requirements
7
7
 
8
- - Python 3.11, 3.12, 3.13, or 3.14 (CI runs all four).
8
+ - Python 3.11, 3.12, 3.13, 3.14, or 3.15 (CI runs all five).
9
9
  - [uv](https://docs.astral.sh/uv/) for environments and dependencies.
10
10
  - A Chromium build for browser tests, installed via `webskrap install`.
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: webskrap
3
- Version: 3.2.3
3
+ Version: 3.2.4
4
4
  Summary: Async Python scraping on Chromium over its own CDP client, with coherent browser profiles and session controls.
5
5
  Project-URL: Homepage, https://github.com/kacigaya/webskrap
6
6
  Project-URL: Repository, https://github.com/kacigaya/webskrap
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Classifier: Typing :: Typed
21
22
  Requires-Python: >=3.11
22
23
  Requires-Dist: mcp<3,>=2
@@ -5,10 +5,10 @@
5
5
  WebSkrap is developed on `main` and released from tags. Fixes land in a new
6
6
  patch release on top of the latest minor; older minors are not backported.
7
7
 
8
- | Version | Supported |
9
- | ----------------------------------------- | --------- |
10
- | Latest minor (2.13.x as of 2026-10-01) | Yes |
11
- | Older minors | No |
8
+ | Version | Supported |
9
+ | ------------ | --------- |
10
+ | Latest minor | Yes |
11
+ | Older minors | No |
12
12
 
13
13
  ## Reporting a vulnerability
14
14
 
@@ -129,9 +129,9 @@ was dated March 2024. Some listed endpoints timed out or rejected authentication
129
129
  The catalog was refreshed through Gluetun's existing updater. The original
130
130
  catalog was backed up, and the VPN was restored to a healthy state.
131
131
 
132
- The final verified exit was `187.13.198.190` in Marseille. It still required
133
- verification. The refreshed catalog is in the container's writable layer;
134
- container recreation can replace it because there is no catalog volume mount.
132
+ The final verified exit still required verification. The refreshed catalog is
133
+ in the container's writable layer; container recreation can replace it because
134
+ there is no catalog volume mount.
135
135
 
136
136
  ## CDP identity investigation
137
137
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "webskrap"
7
- version = "3.2.3"
7
+ version = "3.2.4"
8
8
  description = "Async Python scraping on Chromium over its own CDP client, with coherent browser profiles and session controls."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -22,6 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
24
  "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: 3.15",
25
26
  "Typing :: Typed",
26
27
  ]
27
28
  dependencies = [
@@ -56,8 +57,20 @@ packages = ["src/webskrap"]
56
57
 
57
58
  [tool.hatch.build.targets.sdist]
58
59
  # The web/ frontend ships only to GitHub Pages, never to PyPI. assets/ holds
59
- # the README logo, which no install step reads.
60
- exclude = ["web", ".github", "assets"]
60
+ # the README logo, which no install step reads. The agent skill folders link
61
+ # to one shared references/ copy, and Hatch keeps only one path to a linked
62
+ # directory, so they stay in the Git checkout together with test_docs.py,
63
+ # which checks them.
64
+ exclude = [
65
+ "web",
66
+ ".github",
67
+ "assets",
68
+ ".agents",
69
+ ".claude",
70
+ ".codex",
71
+ "references",
72
+ "tests/test_docs.py",
73
+ ]
61
74
 
62
75
  [tool.pytest.ini_options]
63
76
  asyncio_mode = "auto"
@@ -786,6 +786,8 @@ def test_schema_human_format_lists_commands() -> None:
786
786
 
787
787
 
788
788
  def test_fetch_stays_sandboxed_by_default(monkeypatch: Any) -> None:
789
+ # The host can opt out globally; this test checks the built-in default.
790
+ monkeypatch.delenv("WEBSKRAP_CHROMIUM_SANDBOX", raising=False)
789
791
  _fake_client(monkeypatch)
790
792
 
791
793
  result = runner.invoke(cli.app, ["fetch", "https://example.test", "--format", "json"])
@@ -1163,7 +1163,7 @@ wheels = [
1163
1163
 
1164
1164
  [[package]]
1165
1165
  name = "webskrap"
1166
- version = "3.2.3"
1166
+ version = "3.2.4"
1167
1167
  source = { editable = "." }
1168
1168
  dependencies = [
1169
1169
  { name = "mcp" },
@@ -1,151 +0,0 @@
1
- ---
2
- name: webskrap
3
- description: Use when writing, debugging, documenting, or reviewing Python scraping, web search, and visual browser automation with WebSkrap. Not for ordinary HTTP clients or search APIs.
4
- ---
5
-
6
- # WebSkrap
7
-
8
- WebSkrap is an async Python scraping package that drives Chromium through its
9
- own Chrome DevTools Protocol (CDP) client, with no Playwright. The Python API,
10
- `webskrap` CLI, and `webskrap-mcp` server share core behavior.
11
-
12
- WebSkrap loads known URLs. To find URLs it loads DuckDuckGo or Bing's
13
- results page in the same browser and parses the hits. It calls no search API
14
- and it does not offer Google.
15
-
16
- ## Workflow
17
-
18
- 1. Identify the affected public surface: Python, CLI, MCP, or more than one.
19
- 2. Read its implementation and nearest tests. Trace calls into shared code
20
- before editing.
21
- 3. Change the shared implementation when behavior belongs to more than one
22
- surface. Do not patch each wrapper independently.
23
- 4. Update affected public contracts: models, serialization, errors, command
24
- help, MCP schemas, guides, and tests.
25
- 5. Run focused tests during development, then the full validation gate before
26
- handoff when practical.
27
-
28
- ## Route by task
29
-
30
- | Task | Implementation | Tests and contracts |
31
- | --- | --- | --- |
32
- | Fetching, sessions, links, or screenshots | `src/webskrap/client.py`, `src/webskrap/models.py` | `tests/test_client_unit.py`, `tests/test_models.py` |
33
- | Search engines, redirect unwrapping, or results parsing | `src/webskrap/search.py`, `src/webskrap/models.py` | `tests/test_search.py` and its fixtures under `tests/fixtures/search/` |
34
- | Parsing | `src/webskrap/parsing.py` | `tests/test_parsing.py` |
35
- | Errors, hints, or exit statuses | `src/webskrap/errors.py`, `src/webskrap/cli_output.py` | `tests/test_errors.py`, CLI and MCP callers |
36
- | Main CLI commands | `src/webskrap/cli.py` | `tests/test_cli.py`, `tests/test_diagnostics.py` |
37
- | Persistent browser state | `src/webskrap/browser_session.py`, `src/webskrap/paths.py` | `tests/test_browser_session_unit.py`, `tests/test_paths.py` |
38
- | Interactive browser commands | `src/webskrap/browser_cli.py`, `src/webskrap/mcp_server.py` | `tests/test_browser_cli.py`, `tests/test_browser_integration.py`, `tests/test_mcp.py` |
39
- | MCP tools, resources, or annotations | `src/webskrap/mcp_server.py` | `tests/test_mcp.py`, `src/webskrap/guide.md` |
40
- | Profiles or stealth defaults | `src/webskrap/profiles.py`, `src/webskrap/models.py`, `src/webskrap/client.py` | `tests/test_profiles.py`, opt-in bot-detection tests when needed |
41
- | Cookie rejection | `src/webskrap/consent.py` | `tests/test_consent.py`; `tests/test_consent_live.py` only for selector rot |
42
- | Documentation and examples | `README.md`, `src/webskrap/guide.md` | `tests/test_docs.py`, current CLI help, public Python exports |
43
-
44
- Search for every constructor, serializer, command, and schema that uses a
45
- changed model or field. Fetch and search behavior cross Python, CLI, and MCP.
46
- Persistent browser behavior crosses the browser CLI and MCP. Engine markup
47
- lives only in `src/webskrap/search.py`. When an engine changes its page,
48
- refresh that engine's fixture and fix that one extractor.
49
-
50
- ## Choose the public surface
51
-
52
- | Need | Python | CLI | MCP |
53
- | --- | --- | --- | --- |
54
- | Fetch one known URL | `client.fetch()` | `webskrap fetch` | `stealth_fetch` |
55
- | Find URLs for a query | `client.search()` | `webskrap search` | `search` |
56
- | Preserve cookies or storage | `client.session()` | `webskrap browser open` | `browser_open` |
57
- | See a rendered page or interact with canvas | `browser_session.view()` on a session page | `webskrap browser view` and `mouse` | `browser_view` and `browser_mouse` |
58
- | Click, fill, wait, or run a flow | `session.new_page()` and `human_click()` | `webskrap browser` | `browser_interact` and related tools |
59
- | Diagnose installation | Inspect raised error | `webskrap doctor` | `doctor` |
60
-
61
- Use a one-shot fetch for one page. Use a persistent browser only when state or
62
- interaction must survive between operations. Do not repeatedly fetch a page to
63
- drive a multi-step flow.
64
-
65
- Read only the reference needed for the task:
66
-
67
- - [Python API](references/python-api.md): sessions, pages, profile modes, consent, links,
68
- and fingerprint controls.
69
- - [CLI](references/cli.md): one-shot fetches, persistent browser commands, and
70
- Linux ARM64 behavior.
71
- - [MCP](references/mcp.md): tool selection, confined paths, annotations, and
72
- keeping tool results small.
73
-
74
- ## Visual browser interaction
75
-
76
- Use `browser_view` (MCP) or `webskrap browser view --format json` (CLI) when
77
- layout, images, canvas, or controls missing from the accessibility tree matter.
78
- MCP returns an image block directly. CLI saves a PNG; open its returned `path`
79
- with the agent's image-viewing tool before choosing coordinates. A path or
80
- base64 text alone does not give a model vision. The client and model must
81
- support image input.
82
-
83
- The view covers the viewport at one PNG pixel per CSS pixel. Coordinates start
84
- at its top-left; use the original `width` and `height` if the image viewer
85
- resizes it. Do not add `scroll_x` or `scroll_y` to mouse coordinates. Full-page
86
- screenshots are for inspection or export and do not map directly to the mouse.
87
-
88
- Prefer refs/selectors when they identify a control. Use `browser_mouse` / CLI
89
- `mouse` for visual targets, then `browser_insert_text` / CLI `insert-text` for
90
- literal text in the focused control and `browser_press` / CLI `press` for keys.
91
- Wait for the expected change and take a fresh view after scrolling, navigation,
92
- or layout changes. Coordinate clicks hit whatever occupies that point.
93
- Run dependent actions sequentially within a session.
94
-
95
- This is the browser's rendered page, available headless without Xvfb. It does
96
- not show browser chrome, OS dialogs, or a desktop stream. One-shot fetch
97
- sessions are separate; open a persistent browser to drive a visual flow.
98
- Read the [CLI](references/cli.md) or [MCP](references/mcp.md) reference for examples.
99
-
100
- ## Guardrails
101
-
102
- - Use public pages, local test servers, or targets the user is authorized to
103
- access.
104
- - Do not add CAPTCHA solving, login-wall bypass, credential bypass, or
105
- access-control circumvention.
106
- - Do not commit cookies, storage state, proxy credentials, or persistent browser
107
- data such as `.webskrap/`.
108
- - Preserve Chromium's sandbox by default. Any opt-out must remain explicit and
109
- outside page-controlled input.
110
- - Keep MCP file paths confined to their configured roots. Reject absolute paths,
111
- traversal, and symlink escapes.
112
- - Prefer public `webskrap` exports. Use private helpers only in tests.
113
- - Keep changes typed. Add focused tests for parsing, state transitions, output
114
- contracts, and tool-safety behavior.
115
-
116
- ## Public contract checklist
117
-
118
- When public behavior changes, check all applicable items:
119
-
120
- - Python models and exports
121
- - CLI arguments, help, JSON output, and exit status
122
- - MCP input schema, result shape, annotations, and server instructions
123
- - `README.md` and `src/webskrap/guide.md`
124
- - unit tests for each affected surface
125
-
126
- Use the implementation as the source of truth. In particular, read
127
- `src/webskrap/models.py` for result fields, `src/webskrap/errors.py` for error
128
- codes and exits, and `src/webskrap/diagnostics.py` for environment reporting.
129
- Do not copy these catalogs into the skill.
130
-
131
- ## Validation
132
-
133
- Run the nearest tests while editing. Before handoff, run the repository gate
134
- when practical:
135
-
136
- ```bash
137
- uv run --extra dev pytest -q -m "not browser and not live"
138
- uv run --extra dev ruff check .
139
- uv run --extra dev ruff format --check .
140
- uvx --from pyright==1.1.411 pyright --pythonpath .venv/bin/python src/webskrap
141
- uv run --extra dev bandit -c pyproject.toml -q -r src/
142
- uv run --extra dev --with pip-audit==2.9.0 pip-audit
143
- uv build
144
- ```
145
-
146
- Run browser tests when browser behavior changes and the required browsers are
147
- installed.
148
-
149
- Use `WEBSKRAP_LIVE=1 pytest -q -m live` only when explicitly checking public
150
- third-party behavior. Live tests are non-deterministic and do not replace the
151
- default suite.
@@ -1,154 +0,0 @@
1
- # CLI
2
-
3
- Read this reference when changing or using `webskrap` commands. Treat
4
- `src/webskrap/cli.py` and `src/webskrap/browser_cli.py` as the source of truth
5
- for arguments and help text.
6
-
7
- ## One-shot commands
8
-
9
- `webskrap fetch` runs headless Chrome through WebSkrap's own CDP client. `webskrap install`
10
- downloads Chrome for Testing into the user cache folder
11
- (`WEBSKRAP_BROWSERS_PATH` overrides it); `--force` downloads it again. `doctor` reports
12
- readiness, browser identity, host CPU architecture, and Fontconfig family count.
13
- It warns when the host timezone is UTC. `fetch` and `search` try Chrome, Edge,
14
- then Chrome for Testing when a channel cannot launch. `schema` describes commands
15
- as JSON.
16
-
17
- ```bash
18
- webskrap doctor --format json
19
- webskrap schema
20
- webskrap profiles --format json
21
- webskrap fetch https://example.com --profile desktop-chrome
22
- webskrap fetch https://example.com --format json --max-chars 4000
23
- webskrap fetch https://example.com --format json --links --max-links 20
24
- webskrap fetch https://example.com --stdout --text-only
25
- webskrap fetch https://example.com --quiet --output page.html
26
- ```
27
-
28
- Use `--offset` with the previous result's next offset to continue reading a
29
- long page. Do not re-fetch with an ever larger limit.
30
-
31
- Pass `--ready-selector '#content'` (CSS or a snapshot ref) to wait for visible content after consent
32
- dismissal. `--timeout-ms` applies separately to navigation and the readiness wait.
33
- A missing selector exits 3 (`timeout`). A known challenge or block page
34
- (DataDome, Cloudflare, Akamai, AWS WAF) exits 11 (`blocked`) without retrying,
35
- including one served with HTTP 200.
36
- Ordinary HTTP error pages still return a result with `ok: false`.
37
-
38
- `webskrap search` loads a results page through the same stealth path and
39
- prints the organic hits. `--engine` is `bing` (default) or `ddg`.
40
-
41
- ```bash
42
- webskrap search "example domain"
43
- webskrap search "example domain" --engine bing --max-results 5 --format json
44
- ```
45
-
46
- A `blocked` failure (exit 11) means the engine served a bot challenge; switch
47
- engine, reuse `--user-data-dir`, or change exit IP instead of retrying.
48
-
49
- On Linux ARM64, the `chrome` channel may be unavailable. `fetch` retries a
50
- failed launch with Chrome for Testing. Pass `--channel chromium` to avoid the
51
- first attempt.
52
-
53
- `--profile-mode` sets how much of the profile pages see: `native` (default),
54
- `locale` or `full`. See the Python API reference.
55
-
56
- ## Persistent browser
57
-
58
- `webskrap browser open` launches detached Chromium. Later commands reconnect
59
- over CDP, act on its current page, and exit. The browser
60
- gets the same automation and headless-screen flags as `fetch`; `--headed`
61
- also drops the `HeadlessChrome` user-agent token. `--proxy URL` routes the
62
- session through a proxy and defaults WebRTC to `disable_non_proxied_udp`;
63
- `--webrtc-ip-handling-policy` overrides it. Both are fixed at launch, and
64
- reopening with different values is refused. The URL never holds credentials:
65
- for an http or https proxy, set `WEBSKRAP_PROXY_USERNAME` and
66
- `WEBSKRAP_PROXY_PASSWORD`, and a helper process answers the proxy.
67
-
68
- ```bash
69
- webskrap browser open https://example.com --decline-cookies
70
- webskrap browser snapshot
71
- webskrap browser click e15
72
- webskrap browser fill "input[name=q]" "chromium"
73
- webskrap browser press Enter
74
- webskrap browser wait --text "Results"
75
- webskrap browser screenshot page.png --full-page
76
- webskrap browser screenshot button.png --target e15 # one element
77
- webskrap browser close
78
- ```
79
-
80
- `click`, `dblclick` and `type` use human mouse and keystroke timing (wheel
81
- scrolling, a curved cursor path, a human button hold, spaced keys) and refuse
82
- a covered target; `fill` sets a value at once.
83
-
84
- Snapshot refs become stale when the DOM changes. Take another snapshot before
85
- retrying. `wait` accepts one condition. Do not guess when callers provide more
86
- than one.
87
-
88
- Targets are snapshot refs or CSS selectors. Playwright selector engines such
89
- as `text=` are not supported. Snapshot refs are stable while the element exists,
90
- also across commands. Iframe content is listed under its `iframe` line. Refs in a frame look
91
- like `f12345e15`. CSS selectors match in the main frame only.
92
-
93
- Sessions default to the name `default`. Profiles live below
94
- `~/.webskrap/browser/<name>/`, unless `WEBSKRAP_BROWSER_DIR` changes the root.
95
- They contain cookies and logins and must remain mode `0700`.
96
-
97
- Keep Chromium's OS sandbox enabled. The explicit opt-outs are
98
- `webskrap browser open --no-sandbox` for one session,
99
- `webskrap fetch --no-sandbox` / `webskrap search --no-sandbox` for one-shot
100
- calls, and `WEBSKRAP_CHROMIUM_SANDBOX=0` for a host. Document that disabling it
101
- leaves a compromised renderer uncontained. Never pass `--no-sandbox` (or its
102
- companions) through `--launch-arg`: those flags are blocked.
103
-
104
- Commands act on the current tab. `tab-list`, `tab-new [url]`,
105
- `tab-select <index>` and `tab-close [index]` manage tabs; a popup or a new tab
106
- becomes current. Current limitations are Chrome for Testing only, no network
107
- mocking, tracing, or video, and history navigation that reloads.
108
-
109
- ## Vision and coordinate input
110
-
111
- ```bash
112
- webskrap browser open https://example.com -s visual
113
- webskrap browser view viewport.png -s visual --format json
114
- # Inspect the PNG with your image-viewing tool before choosing coordinates.
115
- webskrap browser mouse click 120 80 -s visual
116
- webskrap browser insert-text "hello 世界" -s visual
117
- webskrap browser press Enter -s visual
118
- webskrap browser mouse scroll 400 300 --delta-y 500 -s visual
119
- webskrap browser view viewport.png -s visual --format json
120
- # Inspect the new view before choosing the drag points.
121
- webskrap browser mouse drag 100 150 --end-x 250 --end-y 180 -s visual
122
- webskrap browser view viewport.png -s visual --format json
123
- webskrap browser close -s visual
124
- ```
125
-
126
- `view` writes a viewport PNG and returns `path` (absolute), `url`, `title`,
127
- `width`, `height`, `scroll_x`, `scroll_y`, `mime_type`, and `coordinate_system`
128
- (`viewport-css-pixels`). Omitting the path generates a unique PNG in the current
129
- directory. JSON contains metadata, not base64 image data: the agent must open
130
- the file with an image-viewing tool.
131
-
132
- One image pixel equals one CSS pixel, even on high-DPI pages. `(0, 0)` is the
133
- viewport's top-left. Map resized previews back to the original dimensions;
134
- do not add document scroll offsets. `screenshot --full-page` remains available
135
- for export, but its coordinates cannot be used directly with `mouse`.
136
-
137
- `mouse` accepts `click`, `dblclick`, `move`, `scroll`, or `drag`, with required
138
- `x y`. `--button` is `left` (default), `middle`, or `right` for click/double-click/
139
- drag. Drag requires both `--end-x` and `--end-y`. Scroll uses `--delta-x` and
140
- `--delta-y` over the element under `x y`; positive vertical deltas scroll down,
141
- including in nested scroll areas. Non-finite, out-of-viewport, and unused
142
- arguments fail with `usage`. Coordinate input uses direct browser events;
143
- selector-based `click` and `type` retain their human timing and covered-target
144
- checks.
145
-
146
- `insert-text` inserts literal Unicode into the focused control with an input
147
- event. It does not interpret `Enter` as a key or emit per-character keydown/
148
- keyup events. Focus with a click or Tab; use `press` for shortcuts and keys,
149
- or selector-based `type` for controls that require individual keystrokes.
150
-
151
- Take a fresh view after navigation, scrolling, or layout changes. Wheel input
152
- may return before scrolling finishes; wait for the expected page condition.
153
- Prefer refs/selectors for known controls, and run dependent session actions
154
- sequentially. This shows the page viewport, not browser chrome or OS dialogs.
@@ -1,130 +0,0 @@
1
- # MCP
2
-
3
- Read this reference for MCP tools, resources, schemas, annotations, and safety
4
- boundaries. Confirm the current contract in `src/webskrap/mcp_server.py` and
5
- `tests/test_mcp.py`.
6
-
7
- ## Select a tool
8
-
9
- - `stealth_fetch` is the default one-shot fetch. It exposes fingerprint,
10
- WebRTC, user-agent, and persistent-profile controls.
11
- - `fetch` is the simpler one-shot form.
12
- - `search` finds URLs for a query on Bing (default) or DuckDuckGo, with the
13
- same stealth controls as `stealth_fetch`. Google is not offered. Follow up
14
- with `stealth_fetch` on the hits worth reading.
15
- - `doctor` reports readiness, browser identity, CPU architecture, Fontconfig
16
- family count, versions, paths, environment overrides, persistent sessions,
17
- and the host timezone, with a warning when it is UTC.
18
- - `browser_open` and the other `browser_*` tools drive a stateful flow.
19
- `browser_open` takes `webrtc_ip_handling_policy` but no proxy; open a proxied
20
- session with `webskrap browser open --proxy URL` and the tools reuse it.
21
- - `webskrap://guide`, `webskrap://profiles`, and `webskrap://sessions` expose
22
- static guidance and current state as resources.
23
-
24
- One-shot fetches do not share cookies between calls. Use a browser session when
25
- state must persist.
26
-
27
- All tools drive Chromium through WebSkrap's own CDP client. Selectors are
28
- snapshot refs or CSS. `stealth_fetch` takes `profile_mode` (`native`, `locale`
29
- or `full`) to set how much of the profile pages see.
30
-
31
- ## Keep results small
32
-
33
- - `max_chars` defaults to 8000 for fetch, snapshot, text, and eval tools.
34
- Continue with `offset` and `next_text_offset` on stable page text. Fetch
35
- windows navigate again; use `browser_text` in a session to avoid re-fetching.
36
- - `browser_text` reads visible body text and selects the window in the browser,
37
- avoiding a full accessibility tree and full-text transfer. The browser still
38
- computes full body text to report its length. `pattern` returns only matching
39
- lines with `context`, capped by `max_matches`; the regex runs in Python with
40
- a 2-second limit.
41
- - `browser_goto` and `browser_open` take `decline_cookies`;
42
- `browser_screenshot` takes `target` for one element.
43
- - Use `resource_policy=lite` to skip images, fonts, and media.
44
- - Keep `include_links` off unless links are needed.
45
- - Snapshots default to `depth=6`. Set `depth=null` for an unlimited tree,
46
- or reduce depth before increasing `max_chars`.
47
- - Make `browser_eval` return the required value, not the full DOM.
48
-
49
- ## Safety boundaries
50
-
51
- Every tool declares title, read-only, destructive, idempotent, and open-world
52
- annotations. Mark interactions as destructive because clicks and key presses
53
- can submit forms on external sites.
54
-
55
- Keep short operational instructions in the MCP server and long guidance in
56
- `src/webskrap/guide.md`.
57
-
58
- `stealth_fetch.user_data_dir` is relative to the MCP profile root, which
59
- defaults to `~/.webskrap/profiles` and can be moved with
60
- `WEBSKRAP_MCP_PROFILE_DIR`. Tool input must not select an absolute path or
61
- escape that root.
62
-
63
- `browser_screenshot` writes below `./webskrap-output`, unless
64
- `WEBSKRAP_OUTPUT_DIR` changes the root. Reject absolute paths, traversal, and
65
- symlinks that leave it.
66
-
67
- The sandbox opt-out is an environment variable, never an MCP argument. Page
68
- content must not be able to persuade the client to disable the browser sandbox.
69
-
70
- Fetch and navigation URLs must be public `http(s)` without credentials.
71
- Private or local hosts are rejected unless `WEBSKRAP_ALLOW_PRIVATE_NET=1`.
72
-
73
- `browser_eval` runs page script in a possibly logged-in profile: prefer
74
- snapshot, interact, and wait_for, never evaluate page-controlled text, and set
75
- `WEBSKRAP_ALLOW_EVAL=0` where models must not run page script.
76
-
77
- ## Failures
78
-
79
- A known challenge (DataDome, Cloudflare, Akamai, AWS WAF) or provider block
80
- page (Cloudflare, Akamai) arrives as a `blocked` tool error, including pages
81
- served with HTTP 200. Blocks and challenges get different hints.
82
- `browser_goto` reports the same check in its `blocked` key instead of failing. Ordinary HTTP error pages return
83
- normally with `ok: false`. A raised failure includes `code` and `hint`. Read
84
- `src/webskrap/errors.py` for the current catalog rather than duplicating it.
85
-
86
- Both fetch tools accept `ready_selector`, a CSS selector or snapshot ref that must be
87
- visible after consent dismissal. The wait uses `timeout_ms` separately from
88
- navigation; a missing selector returns a `timeout` tool error. It may vary between
89
- calls in a warm session. These checks do not establish that later interactions
90
- will remain free of challenges.
91
-
92
- ## Vision and coordinate input
93
-
94
- `browser_view(session="visual")` returns an inline `image/png` image block,
95
- a text block with metadata, and the same metadata in `structuredContent`:
96
- `url`, `title`, `width`, `height`, `scroll_x`, `scroll_y`, `mime_type`, and
97
- `coordinate_system: "viewport-css-pixels"`. It writes no file. Use it with an
98
- image-capable model and client; `browser_screenshot` remains the file/export tool.
99
-
100
- Open with `browser_open(session="visual", url=...)`, inspect
101
- `browser_view(session="visual")`, then call:
102
-
103
- ```json
104
- {"action": "click", "x": 120, "y": 80, "session": "visual"}
105
- ```
106
-
107
- with `browser_mouse`. Choose coordinates from the observed target. For a focused
108
- input, `browser_insert_text(text="hello 世界", session="visual")` inserts literal
109
- text; `browser_press(key="Enter", session="visual")` sends a key. Insertion emits
110
- an input event, not individual keydown/keyup events. Use `browser_interact` with
111
- `action="type"` when a control requires individual keystrokes.
112
-
113
- Mouse actions are `click`, `dblclick`, `move`, `scroll`, and `drag`. All require
114
- `x` and `y`. `button` is `left` (default), `middle`, or `right`, for click,
115
- double-click, or drag only. Drag also requires `end_x` and `end_y`. Scroll accepts
116
- `delta_x` and `delta_y` in CSS pixels, targeting the element under the pointer;
117
- positive `delta_y` scrolls down. Invalid, non-finite, unused, or out-of-viewport
118
- arguments return a `usage` error before mouse input is sent.
119
-
120
- Use the original PNG dimensions: each image pixel is one CSS pixel, independent
121
- of device scale. `(0,0)` is the viewport's top-left. Map any resized preview back
122
- to `width`/`height`, and do not add scroll offsets. Full-page screenshots do not
123
- share this mouse coordinate mapping.
124
-
125
- Prefer refs/selectors for identifiable controls. Coordinate clicks hit whatever
126
- is visible at the point. After an action, wait for the expected change, then take
127
- a fresh view before choosing more coordinates. Wheel input can finish after the
128
- call returns. Run dependent actions sequentially in each session. The image
129
- shows the page, including canvas and frames, not browser chrome, OS dialogs,
130
- or a live desktop stream. Headless rendering needs no Xvfb.
@@ -1,147 +0,0 @@
1
- # Python API
2
-
3
- Read this reference for Python API work. Confirm signatures and defaults in
4
- `src/webskrap/client.py` and `src/webskrap/models.py` before changing code.
5
-
6
- ## Fetch or session
7
-
8
- Use `WebSkrapClient` as an async context manager. `client.fetch()` handles a
9
- one-shot request. Use `client.session()` when cookies, storage, manual page
10
- work, headed debugging, or interactions must persist.
11
-
12
- ```python
13
- import asyncio
14
-
15
- from webskrap import WebSkrapClient
16
-
17
-
18
- async def main() -> None:
19
- async with WebSkrapClient() as client:
20
- result = await client.fetch("https://example.com", include_links=True)
21
- print(result.status, result.final_url, result.title)
22
- print(result.text[:200])
23
-
24
-
25
- asyncio.run(main())
26
- ```
27
-
28
- WebSkrap 3 drives Chromium through its own CDP client. There is no driver to
29
- choose. Pick the browser with `channel` (`chrome`, `msedge`, `chromium`):
30
-
31
- ```python
32
- from pathlib import Path
33
-
34
- from webskrap import SessionConfig
35
-
36
- config = SessionConfig(
37
- channel="chrome",
38
- headless=True,
39
- user_data_dir=Path(".webskrap/headless-profile"),
40
- )
41
- ```
42
-
43
- `channel=None` or `"chromium"` uses the Chrome for Testing build from
44
- `webskrap install`.
45
-
46
- ## Pages you drive yourself
47
-
48
- `session.new_page()` returns a `webskrap.cdp.CDPPage` with the session's
49
- profile and policies applied. Close it with `session.close_page(page)`.
50
-
51
- ```python
52
- async with WebSkrapClient() as client:
53
- session = await client.session("manual")
54
- page = await session.new_page()
55
- response = await page.goto("https://example.com", wait_until="load", timeout_s=30)
56
- title = await page.evaluate("document.title")
57
- await session.human_click(page, "a")
58
- await session.close_page(page)
59
- ```
60
-
61
- - `page.goto()` returns the CDP `Response` object, for example `status` and
62
- `headers`.
63
- - `page.evaluate()` runs in an isolated world. It shares the DOM with the page
64
- but not the page's JavaScript globals.
65
- - `session.human_click()` and `session.human_type()` take a CSS selector or a
66
- snapshot ref.
67
- - `page.send(method, params)` sends any other CDP command to the page.
68
-
69
- ## Consent and links
70
-
71
- `client.fetch()` and `session.fetch()` accept `ready_selector`, an optional
72
- CSS selector or snapshot ref that must become visible after consent dismissal and before
73
- extraction. Its wait uses `timeout_ms` or `navigation_timeout_ms` separately from
74
- navigation. A missing selector raises a timeout. A blank selector is a usage error.
75
-
76
- Known challenge pages (DataDome, Cloudflare, Akamai, AWS WAF) and provider block pages (Cloudflare, Akamai) raise `WebSkrapError` with
77
- `code=ErrorCode.BLOCKED`, even when served with HTTP 200. `error.hint`
78
- holds a kind-specific hint. Ordinary HTTP errors
79
- still return `FetchResult(ok=False)`. Detection uses active provider markers and
80
- headers; it does not identify every challenge or solve verification widgets.
81
-
82
- Cookie rejection is opt-in for Python callers. The CLI and MCP server enable it
83
- by default.
84
-
85
- ```python
86
- config = SessionConfig(decline_cookies=True, decline_cookies_timeout_ms=2_000)
87
- ```
88
-
89
- `FetchResult.cookie_notice_declined` reports `"cmp"`, `"text"`, or `None`.
90
- `session.decline_cookies(page)` applies the same handling to a page controlled
91
- by the caller.
92
-
93
- Link collection is opt-in with `include_links=True` and capped by `max_links`.
94
- Links resolve against the final URL. Duplicate and `javascript:` targets are
95
- dropped. `links_total` is the count before the cap. A session with
96
- `java_script_enabled=False` cannot collect links.
97
-
98
- ## Search
99
-
100
- `client.search(query, engine=..., max_results=...)` and
101
- `session.search(...)` load the engine's results page through the same fetch
102
- path, so `SessionConfig` proxy, consent, and profile settings apply. Engines
103
- are `SearchEngine.BING` (default) and `SearchEngine.DDG`; Google is not
104
- offered.
105
-
106
- ```python
107
- from webskrap import SearchEngine, WebSkrapClient
108
-
109
- async with WebSkrapClient() as client:
110
- result = await client.search("example domain", engine=SearchEngine.BING, max_results=5)
111
- for hit in result.hits:
112
- print(hit.title, hit.url, hit.snippet)
113
- ```
114
-
115
- `SearchResult.hits` is capped by `max_results`; `hits_total` counts the page.
116
- Destinations are unwrapped from the engine's click redirects and deduplicated.
117
- A bot challenge raises `WebSkrapError` with code `blocked`; nothing is retried.
118
-
119
- ## Detection-sensitive pages
120
-
121
- Headed Chrome is the strongest available mode for strict detection surfaces:
122
-
123
- ```python
124
- config = SessionConfig(channel="chrome", headless=False)
125
- ```
126
-
127
- For fingerprint-statistics or WebRTC leak-test pages, prefer native browser
128
- controls over JavaScript patches:
129
-
130
- ```python
131
- config = SessionConfig(
132
- channel="chrome",
133
- headless=True,
134
- profile_mode="locale",
135
- reduce_fingerprint_surface=True,
136
- virtual_display=True,
137
- webrtc_ip_handling_policy="disable_non_proxied_udp",
138
- )
139
- ```
140
-
141
- `profile_mode` sets how much of the `BrowserProfile` pages see:
142
-
143
- - `"native"` (default): nothing. The browser shows its own identity.
144
- - `"locale"`: locale, timezone, color scheme, reduced motion and extra headers.
145
- On Linux, locale and timezone come from the browser environment.
146
- - `"full"`: also viewport, screen, device scale, touch and user agent, as
147
- WebSkrap 2.x did. Each override is a surface that a page can cross-check.
@@ -1 +0,0 @@
1
- ../../../.agents/skills/webskrap/SKILL.md
@@ -1 +0,0 @@
1
- ../../../.agents/skills/webskrap/SKILL.md
@@ -1,44 +0,0 @@
1
- """Guard the skill's routing without duplicating generated API catalogs."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
-
7
- ROOT = Path(__file__).resolve().parents[1]
8
- SKILL_DIR = ROOT / ".agents" / "skills" / "webskrap"
9
- SKILL = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
10
-
11
-
12
- def test_skill_routes_to_existing_references() -> None:
13
- for relative_path in (
14
- "references/python-api.md",
15
- "references/cli.md",
16
- "references/mcp.md",
17
- ):
18
- assert f"]({relative_path})" in SKILL
19
- assert (SKILL_DIR / relative_path).is_file()
20
-
21
-
22
- def test_agent_skill_folders_share_one_copy() -> None:
23
- for agent in (".claude", ".codex"):
24
- folder = ROOT / agent / "skills" / "webskrap"
25
- assert (folder / "SKILL.md").resolve() == (SKILL_DIR / "SKILL.md").resolve()
26
- assert (folder / "references").resolve() == (ROOT / "references").resolve()
27
-
28
-
29
- def test_skill_routes_dynamic_contracts_to_their_sources() -> None:
30
- for source in (
31
- "src/webskrap/models.py",
32
- "src/webskrap/errors.py",
33
- "src/webskrap/diagnostics.py",
34
- "src/webskrap/cli.py",
35
- "src/webskrap/browser_cli.py",
36
- "src/webskrap/mcp_server.py",
37
- ):
38
- assert f"`{source}`" in SKILL
39
-
40
-
41
- def test_skill_states_how_search_works_and_what_it_excludes() -> None:
42
- assert "calls no search API" in SKILL
43
- assert "does not offer Google" in SKILL
44
- assert "`src/webskrap/search.py`" in SKILL
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes