docpull 6.0.1__tar.gz → 6.1.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.
Files changed (218) hide show
  1. {docpull-6.0.1/src/docpull.egg-info → docpull-6.1.0}/PKG-INFO +31 -7
  2. {docpull-6.0.1 → docpull-6.1.0}/README.md +19 -1
  3. {docpull-6.0.1 → docpull-6.1.0}/pyproject.toml +13 -6
  4. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/__init__.py +1 -1
  5. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/benchmark.py +2 -2
  6. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/cli.py +35 -1
  7. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/special_cases.py +148 -8
  8. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/trafilatura_extractor.py +1 -1
  9. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/core/fetcher.py +12 -0
  10. docpull-6.1.0/src/docpull/document_parse.py +1204 -0
  11. docpull-6.1.0/src/docpull/document_worker.py +137 -0
  12. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/mcp/server.py +59 -4
  13. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/mcp/tools.py +17 -3
  14. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/config.py +22 -0
  15. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/convert.py +76 -26
  16. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/fetch.py +6 -1
  17. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/metadata.py +4 -0
  18. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/security/download_policy.py +24 -3
  19. {docpull-6.0.1 → docpull-6.1.0/src/docpull.egg-info}/PKG-INFO +31 -7
  20. {docpull-6.0.1 → docpull-6.1.0}/src/docpull.egg-info/SOURCES.txt +2 -0
  21. {docpull-6.0.1 → docpull-6.1.0}/src/docpull.egg-info/requires.txt +10 -3
  22. {docpull-6.0.1 → docpull-6.1.0}/tests/test_ci_policy.py +46 -2
  23. {docpull-6.0.1 → docpull-6.1.0}/tests/test_cli.py +12 -0
  24. {docpull-6.0.1 → docpull-6.1.0}/tests/test_convert_step_new.py +75 -0
  25. docpull-6.1.0/tests/test_document_parse.py +634 -0
  26. {docpull-6.0.1 → docpull-6.1.0}/tests/test_mcp_server.py +9 -0
  27. {docpull-6.0.1 → docpull-6.1.0}/tests/test_release_a_plus_check_script.py +18 -0
  28. docpull-6.1.0/tests/test_reproducible_release_build.py +179 -0
  29. {docpull-6.0.1 → docpull-6.1.0}/tests/test_security_hardening.py +40 -0
  30. {docpull-6.0.1 → docpull-6.1.0}/tests/test_special_cases.py +142 -0
  31. {docpull-6.0.1 → docpull-6.1.0}/tests/test_surface_contract.py +3 -0
  32. docpull-6.0.1/src/docpull/document_parse.py +0 -643
  33. docpull-6.0.1/tests/test_document_parse.py +0 -133
  34. {docpull-6.0.1 → docpull-6.1.0}/LICENSE +0 -0
  35. {docpull-6.0.1 → docpull-6.1.0}/setup.cfg +0 -0
  36. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/__main__.py +0 -0
  37. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/accounting.py +0 -0
  38. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/agent_publish.py +0 -0
  39. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/auth_cli.py +0 -0
  40. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/basis.py +0 -0
  41. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/cache/__init__.py +0 -0
  42. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/cache/frontier.py +0 -0
  43. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/cache/manager.py +0 -0
  44. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/cache/streaming_dedup.py +0 -0
  45. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_aliases.py +0 -0
  46. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_ci.py +0 -0
  47. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/__init__.py +0 -0
  48. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/_legacy_cli.py +0 -0
  49. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/brand.py +0 -0
  50. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/cli.py +0 -0
  51. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/common.py +0 -0
  52. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/dataset.py +0 -0
  53. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/feed.py +0 -0
  54. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/openapi.py +0 -0
  55. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/package.py +0 -0
  56. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/paper.py +0 -0
  57. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/product.py +0 -0
  58. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/repo.py +0 -0
  59. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/schema_extract.py +0 -0
  60. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/search.py +0 -0
  61. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/standards.py +0 -0
  62. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/styleguide.py +0 -0
  63. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/transcript.py +0 -0
  64. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/typed.py +0 -0
  65. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/typed_models.py +0 -0
  66. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/visuals.py +0 -0
  67. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/context_packs/wiki.py +0 -0
  68. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/__init__.py +0 -0
  69. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/article_cleanup.py +0 -0
  70. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/chunking.py +0 -0
  71. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/ensemble.py +0 -0
  72. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/extractor.py +0 -0
  73. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/filings.py +0 -0
  74. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/markdown.py +0 -0
  75. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/conversion/protocols.py +0 -0
  76. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/core/__init__.py +0 -0
  77. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/__init__.py +0 -0
  78. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/_fetch.py +0 -0
  79. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/composite.py +0 -0
  80. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/contracts.py +0 -0
  81. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/crawler.py +0 -0
  82. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/filters.py +0 -0
  83. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/link_extractors/__init__.py +0 -0
  84. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/link_extractors/enhanced.py +0 -0
  85. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/link_extractors/protocols.py +0 -0
  86. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/link_extractors/static.py +0 -0
  87. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/protocols.py +0 -0
  88. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery/sitemap.py +0 -0
  89. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/discovery_cli.py +0 -0
  90. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/doctor.py +0 -0
  91. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/eval_grade.py +0 -0
  92. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/evidence_pack.py +0 -0
  93. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/exports.py +0 -0
  94. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/fixtures/__init__.py +0 -0
  95. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/fixtures/parallel-search-extract.json +0 -0
  96. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/free_core.py +0 -0
  97. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/free_core_smoke.py +0 -0
  98. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/graph.py +0 -0
  99. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/hosted.py +0 -0
  100. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/http/__init__.py +0 -0
  101. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/http/client.py +0 -0
  102. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/http/protocols.py +0 -0
  103. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/http/rate_limiter.py +0 -0
  104. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/judge.py +0 -0
  105. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/local_workflows.py +0 -0
  106. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/mcp/__init__.py +0 -0
  107. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/mcp/sources.py +0 -0
  108. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/metadata_extractor.py +0 -0
  109. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/__init__.py +0 -0
  110. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/document.py +0 -0
  111. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/events.py +0 -0
  112. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/profiles.py +0 -0
  113. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/models/run.py +0 -0
  114. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/monitor.py +0 -0
  115. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/output_contract.py +0 -0
  116. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pack_reader.py +0 -0
  117. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pack_tools.py +0 -0
  118. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/parallel_workflows.py +0 -0
  119. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/parity.py +0 -0
  120. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/parity_cli.py +0 -0
  121. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/passk.py +0 -0
  122. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/__init__.py +0 -0
  123. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/base.py +0 -0
  124. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/manifest.py +0 -0
  125. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/__init__.py +0 -0
  126. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/chunk.py +0 -0
  127. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/dedup.py +0 -0
  128. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/render.py +0 -0
  129. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/save.py +0 -0
  130. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/save_json.py +0 -0
  131. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/save_ndjson.py +0 -0
  132. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/save_okf.py +0 -0
  133. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/save_sqlite.py +0 -0
  134. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/pipeline/steps/validate.py +0 -0
  135. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/policy.py +0 -0
  136. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/policy_cli.py +0 -0
  137. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/project.py +0 -0
  138. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/provider_adapters.py +0 -0
  139. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/provider_capabilities.py +0 -0
  140. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/provider_cli.py +0 -0
  141. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/provider_keys.py +0 -0
  142. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/provider_probes.py +0 -0
  143. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/py.typed +0 -0
  144. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/redaction.py +0 -0
  145. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/rendering.py +0 -0
  146. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/scraper.py +0 -0
  147. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/security/__init__.py +0 -0
  148. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/security/robots.py +0 -0
  149. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/security/url_validator.py +0 -0
  150. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/server.py +0 -0
  151. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/share.py +0 -0
  152. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/skill_export.py +0 -0
  153. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/source_scoring.py +0 -0
  154. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/surface.py +0 -0
  155. {docpull-6.0.1 → docpull-6.1.0}/src/docpull/time_utils.py +0 -0
  156. {docpull-6.0.1 → docpull-6.1.0}/src/docpull.egg-info/dependency_links.txt +0 -0
  157. {docpull-6.0.1 → docpull-6.1.0}/src/docpull.egg-info/entry_points.txt +0 -0
  158. {docpull-6.0.1 → docpull-6.1.0}/src/docpull.egg-info/top_level.txt +0 -0
  159. {docpull-6.0.1 → docpull-6.1.0}/tests/test_accounting.py +0 -0
  160. {docpull-6.0.1 → docpull-6.1.0}/tests/test_auth_cli.py +0 -0
  161. {docpull-6.0.1 → docpull-6.1.0}/tests/test_basis.py +0 -0
  162. {docpull-6.0.1 → docpull-6.1.0}/tests/test_benchmark.py +0 -0
  163. {docpull-6.0.1 → docpull-6.1.0}/tests/test_cache_conditional_get.py +0 -0
  164. {docpull-6.0.1 → docpull-6.1.0}/tests/test_chunking.py +0 -0
  165. {docpull-6.0.1 → docpull-6.1.0}/tests/test_claim_audit_script.py +0 -0
  166. {docpull-6.0.1 → docpull-6.1.0}/tests/test_context_ci.py +0 -0
  167. {docpull-6.0.1 → docpull-6.1.0}/tests/test_context_packs.py +0 -0
  168. {docpull-6.0.1 → docpull-6.1.0}/tests/test_conversion.py +0 -0
  169. {docpull-6.0.1 → docpull-6.1.0}/tests/test_discovery.py +0 -0
  170. {docpull-6.0.1 → docpull-6.1.0}/tests/test_discovery_contracts.py +0 -0
  171. {docpull-6.0.1 → docpull-6.1.0}/tests/test_doctor.py +0 -0
  172. {docpull-6.0.1 → docpull-6.1.0}/tests/test_document_record.py +0 -0
  173. {docpull-6.0.1 → docpull-6.1.0}/tests/test_evidence_pack.py +0 -0
  174. {docpull-6.0.1 → docpull-6.1.0}/tests/test_exports.py +0 -0
  175. {docpull-6.0.1 → docpull-6.1.0}/tests/test_extractor_ensemble.py +0 -0
  176. {docpull-6.0.1 → docpull-6.1.0}/tests/test_feed_pack.py +0 -0
  177. {docpull-6.0.1 → docpull-6.1.0}/tests/test_free_core.py +0 -0
  178. {docpull-6.0.1 → docpull-6.1.0}/tests/test_frontier_resume.py +0 -0
  179. {docpull-6.0.1 → docpull-6.1.0}/tests/test_graph.py +0 -0
  180. {docpull-6.0.1 → docpull-6.1.0}/tests/test_hosted.py +0 -0
  181. {docpull-6.0.1 → docpull-6.1.0}/tests/test_integration.py +0 -0
  182. {docpull-6.0.1 → docpull-6.1.0}/tests/test_judge.py +0 -0
  183. {docpull-6.0.1 → docpull-6.1.0}/tests/test_link_extractors.py +0 -0
  184. {docpull-6.0.1 → docpull-6.1.0}/tests/test_live_web_smoke.py +0 -0
  185. {docpull-6.0.1 → docpull-6.1.0}/tests/test_local_workflows.py +0 -0
  186. {docpull-6.0.1 → docpull-6.1.0}/tests/test_mcp_tools.py +0 -0
  187. {docpull-6.0.1 → docpull-6.1.0}/tests/test_metadata_extractor.py +0 -0
  188. {docpull-6.0.1 → docpull-6.1.0}/tests/test_monitor.py +0 -0
  189. {docpull-6.0.1 → docpull-6.1.0}/tests/test_naming.py +0 -0
  190. {docpull-6.0.1 → docpull-6.1.0}/tests/test_openapi_pack.py +0 -0
  191. {docpull-6.0.1 → docpull-6.1.0}/tests/test_optional_extras_installed.py +0 -0
  192. {docpull-6.0.1 → docpull-6.1.0}/tests/test_output_contract.py +0 -0
  193. {docpull-6.0.1 → docpull-6.1.0}/tests/test_outputs_e2e.py +0 -0
  194. {docpull-6.0.1 → docpull-6.1.0}/tests/test_pack_server.py +0 -0
  195. {docpull-6.0.1 → docpull-6.1.0}/tests/test_pack_tools.py +0 -0
  196. {docpull-6.0.1 → docpull-6.1.0}/tests/test_parallel_workflows.py +0 -0
  197. {docpull-6.0.1 → docpull-6.1.0}/tests/test_parity_workflows.py +0 -0
  198. {docpull-6.0.1 → docpull-6.1.0}/tests/test_passk.py +0 -0
  199. {docpull-6.0.1 → docpull-6.1.0}/tests/test_pipeline.py +0 -0
  200. {docpull-6.0.1 → docpull-6.1.0}/tests/test_policy.py +0 -0
  201. {docpull-6.0.1 → docpull-6.1.0}/tests/test_policy_cli.py +0 -0
  202. {docpull-6.0.1 → docpull-6.1.0}/tests/test_project.py +0 -0
  203. {docpull-6.0.1 → docpull-6.1.0}/tests/test_provider_adapters.py +0 -0
  204. {docpull-6.0.1 → docpull-6.1.0}/tests/test_provider_cli.py +0 -0
  205. {docpull-6.0.1 → docpull-6.1.0}/tests/test_provider_keys.py +0 -0
  206. {docpull-6.0.1 → docpull-6.1.0}/tests/test_provider_probes.py +0 -0
  207. {docpull-6.0.1 → docpull-6.1.0}/tests/test_public_feature_smoke.py +0 -0
  208. {docpull-6.0.1 → docpull-6.1.0}/tests/test_rate_limiter.py +0 -0
  209. {docpull-6.0.1 → docpull-6.1.0}/tests/test_real_feature_smoke_script.py +0 -0
  210. {docpull-6.0.1 → docpull-6.1.0}/tests/test_real_site_regressions.py +0 -0
  211. {docpull-6.0.1 → docpull-6.1.0}/tests/test_rendering.py +0 -0
  212. {docpull-6.0.1 → docpull-6.1.0}/tests/test_save_ndjson.py +0 -0
  213. {docpull-6.0.1 → docpull-6.1.0}/tests/test_save_sqlite.py +0 -0
  214. {docpull-6.0.1 → docpull-6.1.0}/tests/test_share.py +0 -0
  215. {docpull-6.0.1 → docpull-6.1.0}/tests/test_source_scoring.py +0 -0
  216. {docpull-6.0.1 → docpull-6.1.0}/tests/test_time_utils.py +0 -0
  217. {docpull-6.0.1 → docpull-6.1.0}/tests/test_trafilatura_extractor.py +0 -0
  218. {docpull-6.0.1 → docpull-6.1.0}/tests/test_typed_knowledge_packs.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docpull
3
- Version: 6.0.1
3
+ Version: 6.1.0
4
4
  Summary: Declare, sync, diff, and lock context dependencies for AI agents
5
5
  Author-email: Zachary Roth <support@raintree.technology>
6
6
  Maintainer-email: Raintree Technology <support@raintree.technology>
7
7
  License-Expression: MIT
8
- Project-URL: Homepage, https://docpull.raintree.technology
9
- Project-URL: Documentation, https://docpull.raintree.technology
8
+ Project-URL: Homepage, https://github.com/raintree-technology/docpull
9
+ Project-URL: Documentation, https://github.com/raintree-technology/docpull#readme
10
10
  Project-URL: Repository, https://github.com/raintree-technology/docpull
11
11
  Project-URL: Source Code, https://github.com/raintree-technology/docpull
12
12
  Project-URL: Bug Tracker, https://github.com/raintree-technology/docpull/issues
@@ -71,11 +71,16 @@ Requires-Dist: uvicorn>=0.30.0; extra == "serve"
71
71
  Provides-Extra: parquet
72
72
  Requires-Dist: pyarrow>=16.0.0; extra == "parquet"
73
73
  Provides-Extra: markitdown
74
- Requires-Dist: markitdown>=0.1.0; extra == "markitdown"
74
+ Requires-Dist: pypdf>=6.14.2; extra == "markitdown"
75
+ Requires-Dist: markitdown[docx,pdf]>=0.1.0; extra == "markitdown"
75
76
  Provides-Extra: unstructured
77
+ Requires-Dist: pypdf>=6.14.2; extra == "unstructured"
76
78
  Requires-Dist: unstructured[all-docs]>=0.16.0; extra == "unstructured"
79
+ Provides-Extra: pdf
80
+ Requires-Dist: pypdf>=6.14.2; extra == "pdf"
77
81
  Provides-Extra: parse
78
- Requires-Dist: markitdown>=0.1.0; extra == "parse"
82
+ Requires-Dist: pypdf>=6.14.2; extra == "parse"
83
+ Requires-Dist: markitdown[docx,pdf]>=0.1.0; extra == "parse"
79
84
  Requires-Dist: unstructured[all-docs]>=0.16.0; extra == "parse"
80
85
  Provides-Extra: presidio
81
86
  Requires-Dist: presidio-analyzer>=2.2.0; extra == "presidio"
@@ -96,7 +101,8 @@ Requires-Dist: python-multipart>=0.0.27; extra == "all"
96
101
  Requires-Dist: starlette>=1.3.1; extra == "all"
97
102
  Requires-Dist: uvicorn>=0.30.0; extra == "all"
98
103
  Requires-Dist: pyarrow>=16.0.0; extra == "all"
99
- Requires-Dist: markitdown>=0.1.0; extra == "all"
104
+ Requires-Dist: pypdf>=6.14.2; extra == "all"
105
+ Requires-Dist: markitdown[docx,pdf]>=0.1.0; extra == "all"
100
106
  Requires-Dist: unstructured[all-docs]>=0.16.0; extra == "all"
101
107
  Requires-Dist: presidio-analyzer>=2.2.0; extra == "all"
102
108
  Requires-Dist: e2b>=2.0.0; extra == "all"
@@ -181,6 +187,12 @@ pages such as blogs, API references, OpenAPI specs, changelogs, vendor pages,
181
187
  product pages, filings, docs sites, and other pages where the useful content is
182
188
  available in HTML or embedded page data.
183
189
 
190
+ Direct fetches also recognize standards-style plain text and common textual
191
+ formats from their response media type, including extensionless URLs. Remote
192
+ documents remain denied by default; explicitly opt into local PDF parsing with
193
+ `docpull URL --remote-documents pdf` after installing `docpull[markitdown]` or
194
+ `docpull[parse]`. This does not enable a browser, cloud parser, or paid route.
195
+
184
196
  DocPull is browser-free by default. JS-only pages are skipped with a clear
185
197
  reason unless you explicitly opt into a local renderer. See
186
198
  [Web Source Boundary](docs/scraping-boundary.md) and
@@ -592,6 +604,18 @@ MarkItDown or Unstructured parsers for complex office/PDF files when installed.
592
604
  Install `docpull[markitdown]`, `docpull[unstructured]`, or `docpull[parse]`
593
605
  for those backends.
594
606
 
607
+ An HTTPS PDF can enter the ordinary fetch contract only with explicit consent:
608
+
609
+ ```bash
610
+ docpull https://example.com/handbook.pdf --remote-documents pdf \
611
+ --remote-document-backend auto -o ./packs/handbook
612
+ ```
613
+
614
+ The response must identify as PDF and contain a PDF signature. DocPull parses
615
+ it locally through a mode-`0600` temporary file, records the source hash and
616
+ parser provenance, and removes the temporary source. Other remote download
617
+ types remain blocked.
618
+
595
619
  Every file-backed run writes `corpus.manifest.json` with stable document IDs,
596
620
  chunk IDs, hashes, output paths, and chunk counts. See
597
621
  [Corpus Manifest](docs/corpus-manifest.md).
@@ -754,7 +778,7 @@ docpull URL --preview-urls
754
778
 
755
779
  ## Links
756
780
 
757
- - [Website](https://docpull.raintree.technology)
781
+ - [Documentation](https://github.com/raintree-technology/docpull#readme)
758
782
  - [PyPI](https://pypi.org/project/docpull/)
759
783
  - [GitHub](https://github.com/raintree-technology/docpull)
760
784
  - [Metrics](METRICS.md)
@@ -63,6 +63,12 @@ pages such as blogs, API references, OpenAPI specs, changelogs, vendor pages,
63
63
  product pages, filings, docs sites, and other pages where the useful content is
64
64
  available in HTML or embedded page data.
65
65
 
66
+ Direct fetches also recognize standards-style plain text and common textual
67
+ formats from their response media type, including extensionless URLs. Remote
68
+ documents remain denied by default; explicitly opt into local PDF parsing with
69
+ `docpull URL --remote-documents pdf` after installing `docpull[markitdown]` or
70
+ `docpull[parse]`. This does not enable a browser, cloud parser, or paid route.
71
+
66
72
  DocPull is browser-free by default. JS-only pages are skipped with a clear
67
73
  reason unless you explicitly opt into a local renderer. See
68
74
  [Web Source Boundary](docs/scraping-boundary.md) and
@@ -474,6 +480,18 @@ MarkItDown or Unstructured parsers for complex office/PDF files when installed.
474
480
  Install `docpull[markitdown]`, `docpull[unstructured]`, or `docpull[parse]`
475
481
  for those backends.
476
482
 
483
+ An HTTPS PDF can enter the ordinary fetch contract only with explicit consent:
484
+
485
+ ```bash
486
+ docpull https://example.com/handbook.pdf --remote-documents pdf \
487
+ --remote-document-backend auto -o ./packs/handbook
488
+ ```
489
+
490
+ The response must identify as PDF and contain a PDF signature. DocPull parses
491
+ it locally through a mode-`0600` temporary file, records the source hash and
492
+ parser provenance, and removes the temporary source. Other remote download
493
+ types remain blocked.
494
+
477
495
  Every file-backed run writes `corpus.manifest.json` with stable document IDs,
478
496
  chunk IDs, hashes, output paths, and chunk counts. See
479
497
  [Corpus Manifest](docs/corpus-manifest.md).
@@ -636,7 +654,7 @@ docpull URL --preview-urls
636
654
 
637
655
  ## Links
638
656
 
639
- - [Website](https://docpull.raintree.technology)
657
+ - [Documentation](https://github.com/raintree-technology/docpull#readme)
640
658
  - [PyPI](https://pypi.org/project/docpull/)
641
659
  - [GitHub](https://github.com/raintree-technology/docpull)
642
660
  - [Metrics](METRICS.md)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "docpull"
7
- version = "6.0.1"
7
+ version = "6.1.0"
8
8
  dynamic = []
9
9
  description = "Declare, sync, diff, and lock context dependencies for AI agents"
10
10
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -123,13 +123,19 @@ parquet = [
123
123
  "pyarrow>=16.0.0",
124
124
  ]
125
125
  markitdown = [
126
- "markitdown>=0.1.0",
126
+ "pypdf>=6.14.2",
127
+ "markitdown[docx,pdf]>=0.1.0",
127
128
  ]
128
129
  unstructured = [
130
+ "pypdf>=6.14.2",
129
131
  "unstructured[all-docs]>=0.16.0",
130
132
  ]
133
+ pdf = [
134
+ "pypdf>=6.14.2",
135
+ ]
131
136
  parse = [
132
- "markitdown>=0.1.0",
137
+ "pypdf>=6.14.2",
138
+ "markitdown[docx,pdf]>=0.1.0",
133
139
  "unstructured[all-docs]>=0.16.0",
134
140
  ]
135
141
  presidio = [
@@ -154,7 +160,8 @@ all = [
154
160
  "starlette>=1.3.1",
155
161
  "uvicorn>=0.30.0",
156
162
  "pyarrow>=16.0.0",
157
- "markitdown>=0.1.0",
163
+ "pypdf>=6.14.2",
164
+ "markitdown[docx,pdf]>=0.1.0",
158
165
  "unstructured[all-docs]>=0.16.0",
159
166
  "presidio-analyzer>=2.2.0",
160
167
  "e2b>=2.0.0",
@@ -179,8 +186,8 @@ dev = [
179
186
  docpull = "docpull.cli:main"
180
187
 
181
188
  [project.urls]
182
- Homepage = "https://docpull.raintree.technology"
183
- Documentation = "https://docpull.raintree.technology"
189
+ Homepage = "https://github.com/raintree-technology/docpull"
190
+ Documentation = "https://github.com/raintree-technology/docpull#readme"
184
191
  Repository = "https://github.com/raintree-technology/docpull"
185
192
  "Source Code" = "https://github.com/raintree-technology/docpull"
186
193
  "Bug Tracker" = "https://github.com/raintree-technology/docpull/issues"
@@ -14,7 +14,7 @@ Usage:
14
14
  print(event)
15
15
  """
16
16
 
17
- __version__ = "6.0.1"
17
+ __version__ = "6.1.0"
18
18
 
19
19
  from .cache import CacheManager, StreamingDeduplicator
20
20
  from .context_ci import CIThresholds, ContextCIError, run_context_ci
@@ -200,8 +200,8 @@ TOOL_DOC_TARGETS: tuple[_BenchmarkTarget, ...] = (
200
200
  _BenchmarkTarget(
201
201
  id="docpull_docs",
202
202
  label="DocPull docs",
203
- url="https://docpull.raintree.technology",
204
- include_domains=("docpull.raintree.technology",),
203
+ url="https://github.com/raintree-technology/docpull",
204
+ include_domains=("github.com",),
205
205
  objective="Build an agent context pack for DocPull documentation",
206
206
  queries=("DocPull documentation CLI provider benchmark docs",),
207
207
  freshness_terms=("changelog", "release", "benchmark"),
@@ -335,6 +335,32 @@ Examples:
335
335
  action="store_true",
336
336
  help="Stream NDJSON records to stdout as each page completes (implies --format ndjson)",
337
337
  )
338
+ parser.add_argument(
339
+ "--remote-documents",
340
+ choices=["off", "pdf"],
341
+ default=None,
342
+ help="Explicitly download and locally parse selected remote document types (default: off)",
343
+ )
344
+ parser.add_argument(
345
+ "--remote-document-backend",
346
+ choices=["auto", "pypdf", "markitdown", "unstructured"],
347
+ default=None,
348
+ help="Local parser backend for --remote-documents (default: auto)",
349
+ )
350
+ parser.add_argument(
351
+ "--remote-document-timeout-seconds",
352
+ type=int,
353
+ default=None,
354
+ metavar="SECONDS",
355
+ help="Wall-time limit for each isolated remote-document parser (default: 60)",
356
+ )
357
+ parser.add_argument(
358
+ "--remote-document-memory-mib",
359
+ type=int,
360
+ default=None,
361
+ metavar="MIB",
362
+ help="Address-space limit for each isolated remote-document parser (default: 1024)",
363
+ )
338
364
 
339
365
  # Crawl settings
340
366
  crawl_group = parser.add_argument_group("crawl settings")
@@ -677,6 +703,14 @@ def run_fetcher(args: argparse.Namespace) -> int:
677
703
  filter_kwargs["enable_special_cases"] = False
678
704
  if args.strict_js_required:
679
705
  filter_kwargs["strict_js_required"] = True
706
+ if args.remote_documents:
707
+ filter_kwargs["remote_documents"] = args.remote_documents
708
+ if args.remote_document_backend:
709
+ filter_kwargs["remote_document_backend"] = args.remote_document_backend
710
+ if args.remote_document_timeout_seconds is not None:
711
+ filter_kwargs["remote_document_timeout_seconds"] = args.remote_document_timeout_seconds
712
+ if args.remote_document_memory_mib is not None:
713
+ filter_kwargs["remote_document_memory_mib"] = args.remote_document_memory_mib
680
714
  if filter_kwargs:
681
715
  config_kwargs["content_filter"] = filter_kwargs
682
716
 
@@ -1164,7 +1198,7 @@ def run_render_cli(argv: list[str]) -> int:
1164
1198
  args.cloud_max_estimated_cost if backend in {"vercel-sandbox", "e2b-sandbox"} else None,
1165
1199
  )
1166
1200
  cloud_estimated_cost = (
1167
- estimate_cloud_render_cost_usd(cast(Literal["vercel-sandbox", "e2b-sandbox"], backend), config)
1201
+ estimate_cloud_render_cost_usd(backend, config)
1168
1202
  if backend in {"vercel-sandbox", "e2b-sandbox"}
1169
1203
  else 0.0
1170
1204
  )
@@ -16,6 +16,7 @@ import json
16
16
  import logging
17
17
  import re
18
18
  from dataclasses import dataclass
19
+ from email.message import Message
19
20
  from typing import Any, Protocol
20
21
  from urllib.parse import urlparse
21
22
 
@@ -710,33 +711,143 @@ class RawTextExtractor:
710
711
 
711
712
  name = "raw_text"
712
713
 
713
- _TEXT_SUFFIXES = (".md", ".markdown", ".mdx", ".txt")
714
+ _TEXT_SUFFIXES = (
715
+ ".csv",
716
+ ".json",
717
+ ".jsonl",
718
+ ".markdown",
719
+ ".md",
720
+ ".mdx",
721
+ ".ndjson",
722
+ ".rst",
723
+ ".text",
724
+ ".tsv",
725
+ ".txt",
726
+ ".xml",
727
+ ".yaml",
728
+ ".yml",
729
+ )
730
+ _TEXT_CONTENT_TYPES = {
731
+ "application/json",
732
+ "application/ld+json",
733
+ "application/x-ndjson",
734
+ "application/xml",
735
+ "application/yaml",
736
+ "application/x-yaml",
737
+ "text/csv",
738
+ "text/markdown",
739
+ "text/plain",
740
+ "text/tab-separated-values",
741
+ "text/x-markdown",
742
+ "text/x-rst",
743
+ "text/xml",
744
+ "text/yaml",
745
+ }
714
746
  _HTML_MARKERS = ("<html", "<body", "<!doctype")
715
-
716
- def try_extract(self, html: bytes, url: str) -> SpecialCaseResult | None:
747
+ _STRUCTURED_LANGUAGES = {
748
+ ".csv": "csv",
749
+ ".json": "json",
750
+ ".jsonl": "ndjson",
751
+ ".ndjson": "ndjson",
752
+ ".rst": "rst",
753
+ ".tsv": "tsv",
754
+ ".xml": "xml",
755
+ ".yaml": "yaml",
756
+ ".yml": "yaml",
757
+ }
758
+ _MEDIA_LANGUAGES = {
759
+ "application/json": "json",
760
+ "application/ld+json": "json",
761
+ "application/x-ndjson": "ndjson",
762
+ "application/xml": "xml",
763
+ "application/yaml": "yaml",
764
+ "application/x-yaml": "yaml",
765
+ "text/csv": "csv",
766
+ "text/tab-separated-values": "tsv",
767
+ "text/x-rst": "rst",
768
+ "text/xml": "xml",
769
+ "text/yaml": "yaml",
770
+ }
771
+
772
+ def try_extract(
773
+ self,
774
+ html: bytes,
775
+ url: str,
776
+ *,
777
+ content_type: str | None = None,
778
+ ) -> SpecialCaseResult | None:
717
779
  parsed = urlparse(url)
718
780
  path = parsed.path.lower()
719
- if not path.endswith(self._TEXT_SUFFIXES):
781
+ media_type = self._media_type(content_type)
782
+ identified_by_type = media_type in self._TEXT_CONTENT_TYPES
783
+ if not identified_by_type and not path.endswith(self._TEXT_SUFFIXES):
720
784
  return None
721
785
 
722
- text = _decode_html(html).strip()
786
+ text = self._decode_text(html, content_type).strip()
723
787
  if not text:
724
788
  return None
789
+ language = self._structured_language(path, media_type)
725
790
  lower_head = text[:500].lower()
726
- if any(marker in lower_head for marker in self._HTML_MARKERS):
791
+ if language is None and any(marker in lower_head for marker in self._HTML_MARKERS):
727
792
  return None
728
- if path.endswith(".txt") and not self._looks_like_docs_text(text, path):
793
+ if (
794
+ path.endswith((".txt", ".text"))
795
+ and not identified_by_type
796
+ and not self._looks_like_docs_text(text, path)
797
+ ):
729
798
  return None
730
799
 
731
800
  title = self._extract_title(text) or parsed.path.rsplit("/", 1)[-1] or "Document"
732
801
  source_type = "llms_txt" if path.endswith(("/llms.txt", "/llms-full.txt")) else self.name
802
+ markdown = self._fenced(text, language) if language else self._safe_direct_markdown(text)
733
803
  return SpecialCaseResult(
734
- markdown=text.rstrip() + "\n",
804
+ markdown=markdown,
735
805
  title=title,
736
806
  source_type=source_type,
737
807
  extra={"framework": source_type},
738
808
  )
739
809
 
810
+ @classmethod
811
+ def _structured_language(cls, path: str, media_type: str) -> str | None:
812
+ if media_type in {"text/markdown", "text/x-markdown"} or path.endswith((".markdown", ".md", ".mdx")):
813
+ return None
814
+ media_language = cls._MEDIA_LANGUAGES.get(media_type)
815
+ if media_language:
816
+ return media_language
817
+ return next(
818
+ (language for suffix, language in cls._STRUCTURED_LANGUAGES.items() if path.endswith(suffix)),
819
+ None,
820
+ )
821
+
822
+ @staticmethod
823
+ def _fenced(text: str, language: str) -> str:
824
+ longest_run = max((len(match.group(0)) for match in re.finditer(r"`+", text)), default=0)
825
+ fence = "`" * max(3, longest_run + 1)
826
+ return f"{fence}{language}\n{text.rstrip()}\n{fence}\n"
827
+
828
+ @staticmethod
829
+ def _safe_direct_markdown(text: str) -> str:
830
+ inert = re.sub(r"<(/?)script\b", r"&lt;\1script", text, flags=re.IGNORECASE)
831
+ return inert.rstrip() + "\n"
832
+
833
+ @staticmethod
834
+ def _media_type(content_type: str | None) -> str:
835
+ return (content_type or "").split(";", 1)[0].strip().casefold()
836
+
837
+ @staticmethod
838
+ def _decode_text(body: bytes, content_type: str | None) -> str:
839
+ if body.startswith(b"\xef\xbb\xbf"):
840
+ return body.decode("utf-8-sig", errors="replace")
841
+ encoding = "utf-8"
842
+ if content_type:
843
+ message = Message()
844
+ message["content-type"] = content_type
845
+ encoding = message.get_content_charset() or encoding
846
+ try:
847
+ return body.decode(encoding)
848
+ except (LookupError, UnicodeDecodeError):
849
+ return body.decode("utf-8", errors="replace")
850
+
740
851
  @staticmethod
741
852
  def _looks_like_docs_text(text: str, path: str) -> bool:
742
853
  if path.endswith(("/llms.txt", "/llms-full.txt")):
@@ -802,6 +913,33 @@ class SphinxObjectsInvExtractor:
802
913
  )
803
914
 
804
915
 
916
+ class RfcEditorExtractor:
917
+ """Preserve complete RFC Editor HTML documents instead of one scored section."""
918
+
919
+ name = "rfc_editor"
920
+
921
+ def try_extract(self, html: bytes, url: str) -> SpecialCaseResult | None:
922
+ parsed = urlparse(url)
923
+ if not _hostname_matches_domain(parsed.hostname or "", "rfc-editor.org"):
924
+ return None
925
+ if re.fullmatch(r"/rfc/rfc\d+\.html", parsed.path.casefold()) is None:
926
+ return None
927
+
928
+ from .extractor import MainContentExtractor
929
+ from .markdown import HtmlToMarkdown
930
+
931
+ extracted_html = MainContentExtractor(content_selectors=["body"]).extract(html, url)
932
+ markdown = HtmlToMarkdown().convert(extracted_html, url).strip()
933
+ if len(markdown) < 80:
934
+ return None
935
+ return SpecialCaseResult(
936
+ markdown=markdown + "\n",
937
+ title=_title_from_soup(_soup(html)),
938
+ source_type=self.name,
939
+ extra={"framework": "rfc_editor"},
940
+ )
941
+
942
+
805
943
  class MdxSourceExtractor:
806
944
  """Rewrite ``edit-this-page`` GitHub links to raw MDX source URLs.
807
945
 
@@ -842,6 +980,7 @@ class MdxSourceExtractor:
842
980
  DEFAULT_CHAIN: list[SpecialCaseExtractor] = [
843
981
  OpenApiExtractor(),
844
982
  RawTextExtractor(),
983
+ RfcEditorExtractor(),
845
984
  MintlifyExtractor(),
846
985
  NextDataExtractor(),
847
986
  MkDocsMaterialExtractor(),
@@ -969,6 +1108,7 @@ __all__ = [
969
1108
  "RawTextExtractor",
970
1109
  "ReadMeExtractor",
971
1110
  "RedocScalarExtractor",
1111
+ "RfcEditorExtractor",
972
1112
  "SpecialCaseExtractor",
973
1113
  "SpecialCaseResult",
974
1114
  "SphinxObjectsInvExtractor",
@@ -63,7 +63,7 @@ class TrafilaturaExtractor:
63
63
  if result is None:
64
64
  logger.debug("trafilatura returned no content for %s", url)
65
65
  return ""
66
- return result.strip() + "\n"
66
+ return str(result).strip() + "\n"
67
67
 
68
68
 
69
69
  __all__ = ["TrafilaturaExtractor"]
@@ -34,6 +34,7 @@ from ..pipeline.steps import (
34
34
  SqliteSaveStep,
35
35
  ValidateStep,
36
36
  )
37
+ from ..security.download_policy import SafeDownloadPolicy
37
38
  from ..security.robots import RobotsChecker
38
39
  from ..security.url_validator import UrlValidator
39
40
 
@@ -331,6 +332,9 @@ class Fetcher:
331
332
  if self.config.content_filter.max_file_size is not None
332
333
  else AsyncHttpClient.MAX_CONTENT_SIZE
333
334
  )
335
+ allowed_remote_document_types = (
336
+ {"application/pdf"} if self.config.content_filter.remote_documents == "pdf" else set()
337
+ )
334
338
  self._http_client = AsyncHttpClient(
335
339
  rate_limiter=self._rate_limiter,
336
340
  max_retries=self.config.network.max_retries,
@@ -344,6 +348,9 @@ class Fetcher:
344
348
  auth_scope_hosts=auth_scope_hosts,
345
349
  require_pinned_dns=self.config.network.require_pinned_dns,
346
350
  log_retry_warnings=self.config.network.log_retry_warnings,
351
+ download_policy=SafeDownloadPolicy(
352
+ allowed_remote_document_types=allowed_remote_document_types,
353
+ ),
347
354
  )
348
355
  await self._http_client.__aenter__()
349
356
 
@@ -396,6 +403,7 @@ class Fetcher:
396
403
  http_client=self._http_client,
397
404
  cache_manager=self._cache_manager,
398
405
  skip_unchanged=self.config.cache.skip_unchanged,
406
+ allowed_remote_document_types=allowed_remote_document_types,
399
407
  )
400
408
  )
401
409
  if render_mode == "fallback":
@@ -417,6 +425,10 @@ class Fetcher:
417
425
  use_ensemble=self.config.content_filter.extractor == "ensemble",
418
426
  strict_js_required=self.config.content_filter.strict_js_required,
419
427
  clean_inline_xbrl=self.config.content_filter.clean_inline_xbrl,
428
+ remote_documents=self.config.content_filter.remote_documents,
429
+ remote_document_backend=self.config.content_filter.remote_document_backend,
430
+ remote_document_timeout_seconds=(self.config.content_filter.remote_document_timeout_seconds),
431
+ remote_document_memory_mib=self.config.content_filter.remote_document_memory_mib,
420
432
  )
421
433
  )
422
434