scitex 2.14.0__py3-none-any.whl → 2.15.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. scitex/__init__.py +47 -0
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +191 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/writer.py +21 -204
  17. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  18. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  19. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  20. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  21. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  22. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  23. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  24. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  25. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  26. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  27. scitex/audio/README.md +40 -36
  28. scitex/audio/__init__.py +127 -59
  29. scitex/audio/_branding.py +185 -0
  30. scitex/audio/_mcp/__init__.py +32 -0
  31. scitex/audio/_mcp/handlers.py +59 -6
  32. scitex/audio/_mcp/speak_handlers.py +238 -0
  33. scitex/audio/_relay.py +225 -0
  34. scitex/audio/engines/elevenlabs_engine.py +6 -1
  35. scitex/audio/mcp_server.py +228 -75
  36. scitex/canvas/README.md +1 -1
  37. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  38. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  39. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  40. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  41. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  42. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  43. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  44. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  45. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  46. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  47. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  48. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  49. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  50. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  51. scitex/cli/__init__.py +38 -43
  52. scitex/cli/audio.py +76 -27
  53. scitex/cli/capture.py +13 -20
  54. scitex/cli/introspect.py +443 -0
  55. scitex/cli/main.py +198 -109
  56. scitex/cli/mcp.py +60 -34
  57. scitex/cli/scholar/__init__.py +8 -0
  58. scitex/cli/scholar/_crossref_scitex.py +296 -0
  59. scitex/cli/scholar/_fetch.py +25 -3
  60. scitex/cli/social.py +314 -0
  61. scitex/cli/writer.py +117 -0
  62. scitex/config/README.md +1 -1
  63. scitex/config/__init__.py +16 -2
  64. scitex/config/_env_registry.py +191 -0
  65. scitex/diagram/__init__.py +42 -19
  66. scitex/diagram/mcp_server.py +13 -125
  67. scitex/introspect/__init__.py +75 -0
  68. scitex/introspect/_call_graph.py +303 -0
  69. scitex/introspect/_class_hierarchy.py +163 -0
  70. scitex/introspect/_core.py +42 -0
  71. scitex/introspect/_docstring.py +131 -0
  72. scitex/introspect/_examples.py +113 -0
  73. scitex/introspect/_imports.py +271 -0
  74. scitex/introspect/_mcp/__init__.py +37 -0
  75. scitex/introspect/_mcp/handlers.py +208 -0
  76. scitex/introspect/_members.py +151 -0
  77. scitex/introspect/_resolve.py +89 -0
  78. scitex/introspect/_signature.py +131 -0
  79. scitex/introspect/_source.py +80 -0
  80. scitex/introspect/_type_hints.py +172 -0
  81. scitex/io/bundle/README.md +1 -1
  82. scitex/mcp_server.py +98 -5
  83. scitex/plt/__init__.py +248 -550
  84. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  85. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  86. scitex/plt/gallery/README.md +1 -1
  87. scitex/plt/utils/_hitmap/__init__.py +82 -0
  88. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  89. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  90. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  91. scitex/plt/utils/_hitmap/_constants.py +40 -0
  92. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  93. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  94. scitex/plt/utils/_hitmap/_query.py +113 -0
  95. scitex/plt/utils/_hitmap.py +46 -1616
  96. scitex/plt/utils/_metadata/__init__.py +80 -0
  97. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  98. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  99. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  100. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  101. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  102. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  103. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  104. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  105. scitex/plt/utils/_metadata/_csv.py +416 -0
  106. scitex/plt/utils/_metadata/_detect.py +225 -0
  107. scitex/plt/utils/_metadata/_legend.py +127 -0
  108. scitex/plt/utils/_metadata/_rounding.py +117 -0
  109. scitex/plt/utils/_metadata/_verification.py +202 -0
  110. scitex/schema/README.md +1 -1
  111. scitex/scholar/__init__.py +8 -0
  112. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  113. scitex/scholar/core/Scholar.py +63 -1700
  114. scitex/scholar/core/_mixins/__init__.py +36 -0
  115. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  116. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  117. scitex/scholar/core/_mixins/_loaders.py +103 -0
  118. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  119. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  120. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  121. scitex/scholar/core/_mixins/_savers.py +69 -0
  122. scitex/scholar/core/_mixins/_search.py +103 -0
  123. scitex/scholar/core/_mixins/_services.py +88 -0
  124. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  125. scitex/scholar/crossref_scitex.py +367 -0
  126. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  127. scitex/scholar/examples/00_run_all.sh +120 -0
  128. scitex/scholar/jobs/_executors.py +27 -3
  129. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  130. scitex/scholar/pdf_download/_cli.py +154 -0
  131. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  132. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  133. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  134. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  135. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  136. scitex/scholar/pipelines/_single_steps.py +71 -36
  137. scitex/scholar/storage/_LibraryManager.py +97 -1695
  138. scitex/scholar/storage/_mixins/__init__.py +30 -0
  139. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  140. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  141. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  142. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  143. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  144. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  145. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  146. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  147. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  148. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  149. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  150. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  151. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  152. scitex/security/README.md +3 -3
  153. scitex/session/README.md +1 -1
  154. scitex/sh/README.md +1 -1
  155. scitex/social/__init__.py +153 -0
  156. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  157. scitex/template/README.md +1 -1
  158. scitex/template/clone_writer_directory.py +5 -5
  159. scitex/writer/README.md +1 -1
  160. scitex/writer/_mcp/handlers.py +11 -744
  161. scitex/writer/_mcp/tool_schemas.py +5 -335
  162. scitex-2.15.1.dist-info/METADATA +648 -0
  163. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
  164. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  165. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  166. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  167. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  168. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  169. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  170. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  171. scitex/diagram/_compile.py +0 -312
  172. scitex/diagram/_diagram.py +0 -355
  173. scitex/diagram/_mcp/__init__.py +0 -4
  174. scitex/diagram/_mcp/handlers.py +0 -400
  175. scitex/diagram/_mcp/tool_schemas.py +0 -157
  176. scitex/diagram/_presets.py +0 -173
  177. scitex/diagram/_schema.py +0 -182
  178. scitex/diagram/_split.py +0 -278
  179. scitex/plt/_mcp/__init__.py +0 -4
  180. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  181. scitex/plt/_mcp/_handlers_figure.py +0 -195
  182. scitex/plt/_mcp/_handlers_plot.py +0 -252
  183. scitex/plt/_mcp/_handlers_style.py +0 -219
  184. scitex/plt/_mcp/handlers.py +0 -74
  185. scitex/plt/_mcp/tool_schemas.py +0 -497
  186. scitex/plt/mcp_server.py +0 -231
  187. scitex/scholar/data/.gitkeep +0 -0
  188. scitex/scholar/data/README.md +0 -44
  189. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  190. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  191. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  192. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  193. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  194. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  195. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  196. scitex/scholar/data/bib_files/pac.bib +0 -698
  197. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  198. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  199. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  200. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  201. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  202. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  203. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  204. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  205. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  206. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  207. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  208. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  209. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  210. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  211. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  212. scitex/scholar/data/impact_factor.db +0 -0
  213. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  214. scitex/scholar/examples/dev.py +0 -38
  215. scitex-2.14.0.dist-info/METADATA +0 -1238
  216. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
  217. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
  218. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
1
+ <!-- ---
2
+ !-- Timestamp: 2025-08-03 00:51:52
3
+ !-- Author: ywatanabe
4
+ !-- File: /home/ywatanabe/proj/scitex_repo/src/scitex/scholar/open_url/README.md
5
+ !-- --- -->
6
+
7
+ # OpenURL Resolvers
8
+
9
+ This module provides OpenURL resolver implementations with automatic ZenRows integration when API key is present.
10
+
11
+ **Key Feature**: ZenRows stealth browser is automatically enabled when `SCITEX_SCHOLAR_ZENROWS_API_KEY` is set, providing:
12
+ - 🛡️ Anti-bot protection with residential IPs
13
+ - 🌐 Full browser control for authentication
14
+ - 🚀 Automatic bypass of rate limits and CAPTCHAs
15
+
16
+ ## 1. OpenURLResolver (Standard)
17
+
18
+ The standard browser-based resolver using Playwright.
19
+
20
+ **Best for:**
21
+ - Authenticated access to paywalled content
22
+ - Complex JavaScript-based authentication flows
23
+ - Sites that require real browser interactions
24
+
25
+ **Limitations:**
26
+ - Can be blocked by anti-bot measures
27
+ - May encounter CAPTCHAs or rate limits
28
+
29
+ ```python
30
+ from scitex.scholar.open_url import OpenURLResolver
31
+ from scitex.scholar.auth import AuthenticationManager
32
+
33
+ auth_manager = AuthenticationManager(email_openathens="your@email.com")
34
+ resolver = OpenURLResolver(auth_manager, "https://your.resolver.url/")
35
+
36
+ result = await resolver.resolve_async(doi="10.1038/nature12373")
37
+ ```
38
+
39
+ ## 2. OpenURLResolverWithZenRows (API-based)
40
+
41
+ Uses ZenRows API to bypass anti-bot detection while making HTTP requests.
42
+
43
+ **Best for:**
44
+ - High-volume resolution tasks
45
+ - Bypassing rate limits and IP blocks
46
+ - Open access content detection
47
+
48
+ **Limitations:**
49
+ - Cannot execute JavaScript (no popup handling)
50
+ - Limited authentication cookie transfer to publishers
51
+ - May show_async "Purchase" for paywalled content even with auth
52
+
53
+ ```python
54
+ from scitex.scholar.open_url import OpenURLResolverWithZenRows
55
+
56
+ resolver = OpenURLResolverWithZenRows(
57
+ auth_manager,
58
+ resolver_url,
59
+ zenrows_api_key="your_api_key" # or set SCITEX_SCHOLAR_ZENROWS_API_KEY
60
+ )
61
+
62
+ result = await resolver.resolve_async(doi="10.1038/nature12373")
63
+ ```
64
+
65
+ ## 3. ZenRowsOpenURLResolver (Browser-based)
66
+
67
+ Uses ZenRows Scraping Browser service - cloud-based Chrome instances with anti-bot bypass.
68
+
69
+ **Best for:**
70
+ - Sites with aggressive anti-bot protection (e.g., PNAS)
71
+ - Maintaining full authentication context
72
+ - JavaScript-heavy authentication flows with anti-bot measures
73
+
74
+ **Limitations:**
75
+ - Requires ZenRows API key
76
+ - Slightly slower due to remote browser
77
+ - May have concurrency limits based on plan
78
+
79
+ ```python
80
+ from scitex.scholar.open_url import ZenRowsOpenURLResolver
81
+
82
+ resolver = ZenRowsOpenURLResolver(
83
+ auth_manager,
84
+ resolver_url,
85
+ zenrows_api_key="your_api_key" # or set SCITEX_SCHOLAR_ZENROWS_API_KEY
86
+ )
87
+
88
+ result = await resolver.resolve_async(doi="10.1073/pnas.0608765104")
89
+ ```
90
+
91
+ ## Usage Example (Synchronous)
92
+
93
+ ```python
94
+ from scitex.scholar.open_url import OpenURLResolver, ZenRowsOpenURLResolver
95
+ from scitex.scholar.auth import AuthenticationManager
96
+ import os
97
+ from scitex import logging
98
+
99
+ # Enable debug logging
100
+ logger = logging.getLogger()
101
+ logger.setLevel(logging.DEBUG)
102
+
103
+ # Initialize authentication
104
+ auth_manager = AuthenticationManager(
105
+ email_openathens=os.getenv("SCITEX_SCHOLAR_OPENATHENS_EMAIL")
106
+ )
107
+ is_authenticate_async = await auth_manager.is_authenticate_async()
108
+
109
+ # Choose your resolver
110
+ # Standard browser-based resolver
111
+ resolver = OpenURLResolver(
112
+ auth_manager,
113
+ os.getenv("SCITEX_SCHOLAR_OPENURL_RESOLVER_URL")
114
+ )
115
+
116
+
117
+ # # OR: ZenRows cloud browser resolver (for anti-bot bypass)
118
+ # resolver = ZenRowsOpenURLResolver(
119
+ # auth_manager,
120
+ # os.getenv("SCITEX_SCHOLAR_OPENURL_RESOLVER_URL"),
121
+ # os.getenv("SCITEX_SCHOLAR_ZENROWS_API_KEY"))
122
+
123
+
124
+ # DOIs to resolve
125
+ dois = [
126
+ "10.1038/nature12373",
127
+ "10.1016/j.neuron.2018.01.048",
128
+ "10.1126/science.1172133",
129
+ "10.1073/pnas.0608765104",
130
+ ]
131
+
132
+ # "10.1002/hipo.22488",
133
+ # # Resolve single DOI
134
+ # result = resolver._resolve_single(doi=dois[0])
135
+
136
+ # Resolve multiple DOIs in parallel
137
+ results = resolver.resolve(dois)
138
+ ```
139
+
140
+ ## Choosing the Right Resolver
141
+
142
+ | Scenario | Recommended Resolver |
143
+ |----------|---------------------|
144
+ | General academic paper access | OpenURLResolver |
145
+ | High-volume batch processing | OpenURLResolverWithZenRows |
146
+ | Sites blocking normal browsers | ZenRowsOpenURLResolver |
147
+ | PNAS, sites with "unusual traffic" errors | ZenRowsOpenURLResolver |
148
+ | Need full JavaScript execution + anti-bot | ZenRowsOpenURLResolver |
149
+
150
+ ## Automatic Fallback Strategy
151
+
152
+ You can implement automatic fallback between resolvers:
153
+
154
+ ```python
155
+ async def resolve_with_fallback_async(doi, metadata):
156
+ # Try standard resolver first
157
+ result = await standard_resolver.resolve_async(doi=doi, **metadata)
158
+
159
+ if result and result.get('success'):
160
+ return result
161
+
162
+ # Check for anti-bot indicators
163
+ if result and result.get('access_type') in ['captcha_required', 'rate_limited']:
164
+ # Try ZenRows browser resolver
165
+ return await zenrows_browser_resolver.resolve_async(doi=doi, **metadata)
166
+
167
+ return result
168
+ ```
169
+
170
+ ## NEW: Simplified ZenRows Stealth Browser (Recommended)
171
+
172
+ As of the latest update, ZenRows stealth capabilities are automatically integrated when the API key is present:
173
+
174
+ ```python
175
+ # Just set the API key - ZenRows stealth is automatically enabled!
176
+ os.environ["SCITEX_SCHOLAR_ZENROWS_API_KEY"] = "your_api_key"
177
+
178
+ from scitex.scholar import Scholar
179
+
180
+ # Scholar automatically uses ZenRows stealth browser
181
+ scholar = Scholar()
182
+
183
+ # Download with automatic anti-bot protection
184
+ papers = await scholar.download_pdf_asyncs_async(
185
+ ["10.1038/nature12373", "10.1073/pnas.0608765104"],
186
+ show_async_progress=True
187
+ )
188
+ ```
189
+
190
+ This provides:
191
+ - **Local browser window** you can see and interact with
192
+ - **ZenRows proxy** for clean residential IPs
193
+ - **Manual login** capability for complex SSO/2FA
194
+ - **Automatic anti-bot bypass** for all operations
195
+
196
+ ## Environment Variables
197
+
198
+ - `SCITEX_SCHOLAR_ZENROWS_API_KEY`: Your ZenRows API key (auto-enables stealth)
199
+ - `SCITEX_SCHOLAR_OPENATHENS_EMAIL`: Email for OpenAthens authentication
200
+ - `SCITEX_SCHOLAR_OPENURL_RESOLVER_URL`: Your institutional OpenURL resolver
201
+
202
+ ## Architecture
203
+
204
+ ```
205
+ OpenURL Resolvers
206
+ ├── _OpenURLResolver.py # Base implementation with Playwright
207
+ ├── _OpenURLResolverWithZenRows.py # API-based ZenRows integration
208
+ ├── _ZenRowsOpenURLResolver.py # Browser-based ZenRows integration
209
+ └── _ResolverLinkFinder.py # Shared link detection logic
210
+
211
+ Browser Managers
212
+ ├── _BrowserManager.py # Standard local browser
213
+ ├── _ProxyBrowserManager.py # Local browser + proxy routing
214
+ └── _ZenRowsBrowserManager.py # Cloud browser instances
215
+ ```
216
+
217
+ The separation ensures:
218
+ - Clean architecture with single responsibility
219
+ - Easy switching between implementations
220
+ - No interference with other browser-based operations
221
+ - Flexibility to use different strategies for different papers
222
+
223
+ <!-- EOF -->