scitex 2.14.0__py3-none-any.whl → 2.15.3__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 (264) hide show
  1. scitex/__init__.py +71 -17
  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 +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
scitex/cli/plt.py ADDED
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/cli/plt.py
4
+
5
+ """
6
+ SciTeX CLI - Plot Commands
7
+
8
+ Thin wrapper around figrecipe CLI for reproducible matplotlib figures.
9
+ All commands delegate to figrecipe for reproducibility.
10
+ """
11
+
12
+ import subprocess
13
+ import sys
14
+
15
+ import click
16
+
17
+
18
+ def _run_figrecipe(*args) -> int:
19
+ """Run figrecipe CLI command."""
20
+ cmd = [sys.executable, "-m", "figrecipe"]
21
+ cmd.extend(args)
22
+
23
+ result = subprocess.run(cmd)
24
+ return result.returncode
25
+
26
+
27
+ def _check_figrecipe() -> bool:
28
+ """Check if figrecipe is available."""
29
+ try:
30
+ import figrecipe # noqa: F401
31
+
32
+ return True
33
+ except ImportError:
34
+ return False
35
+
36
+
37
+ @click.group(
38
+ context_settings={"help_option_names": ["-h", "--help"]},
39
+ invoke_without_command=True,
40
+ )
41
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
42
+ @click.pass_context
43
+ def plt(ctx, help_recursive):
44
+ """
45
+ Plot and figure management (powered by figrecipe)
46
+
47
+ \b
48
+ Commands:
49
+ plot - Create figure from YAML/JSON spec
50
+ edit - Launch interactive GUI editor
51
+ compose - Combine multiple figures
52
+ crop - Crop whitespace from images
53
+ reproduce - Reproduce figure from recipe
54
+ validate - Validate recipe reproducibility
55
+ diagram - Create diagrams (flowcharts, etc.)
56
+
57
+ \b
58
+ Examples:
59
+ scitex plt plot spec.yaml -o fig.png
60
+ scitex plt edit
61
+ scitex plt compose a.png b.png -o combined.png
62
+ scitex plt reproduce recipe.yaml
63
+
64
+ \b
65
+ Note: Wraps figrecipe CLI. Run 'figrecipe --help' for full options.
66
+ """
67
+ if not _check_figrecipe():
68
+ click.secho("Error: figrecipe not installed", fg="red", err=True)
69
+ click.echo("\nInstall with: pip install figrecipe")
70
+ ctx.exit(1)
71
+
72
+ if help_recursive:
73
+ from . import print_help_recursive
74
+
75
+ print_help_recursive(ctx, plt)
76
+ ctx.exit(0)
77
+ elif ctx.invoked_subcommand is None:
78
+ click.echo(ctx.get_help())
79
+
80
+
81
+ @plt.command()
82
+ @click.argument("spec_file", type=click.Path(exists=True))
83
+ @click.option("-o", "--output", required=True, help="Output file path")
84
+ @click.option("--dpi", type=int, default=300, help="DPI for raster output")
85
+ @click.option("--no-recipe", is_flag=True, help="Don't save YAML recipe")
86
+ def plot(spec_file, output, dpi, no_recipe):
87
+ """
88
+ Create a figure from a declarative YAML/JSON spec.
89
+
90
+ \b
91
+ Examples:
92
+ scitex plt plot spec.yaml -o fig.png
93
+ scitex plt plot spec.json -o fig.pdf --dpi 600
94
+ """
95
+ args = ["plot", spec_file, "-o", output, "--dpi", str(dpi)]
96
+ if no_recipe:
97
+ args.append("--no-recipe")
98
+ sys.exit(_run_figrecipe(*args))
99
+
100
+
101
+ @plt.command()
102
+ @click.argument("recipe_file", type=click.Path(exists=True), required=False)
103
+ def edit(recipe_file):
104
+ """
105
+ Launch interactive GUI editor.
106
+
107
+ \b
108
+ Examples:
109
+ scitex plt edit # Open blank editor
110
+ scitex plt edit recipe.yaml # Open existing recipe
111
+ """
112
+ args = ["edit"]
113
+ if recipe_file:
114
+ args.append(recipe_file)
115
+ sys.exit(_run_figrecipe(*args))
116
+
117
+
118
+ @plt.command()
119
+ @click.argument("sources", nargs=-1, required=True, type=click.Path(exists=True))
120
+ @click.option("-o", "--output", required=True, help="Output file path")
121
+ @click.option(
122
+ "-l",
123
+ "--layout",
124
+ type=click.Choice(["horizontal", "vertical", "grid"]),
125
+ default="horizontal",
126
+ help="Layout mode",
127
+ )
128
+ @click.option("--gap", type=float, default=5, help="Gap between panels (mm)")
129
+ @click.option("--dpi", type=int, default=300, help="DPI for output")
130
+ @click.option("--no-labels", is_flag=True, help="Don't add panel labels (A, B, C)")
131
+ @click.option("--caption", help="Figure caption")
132
+ def compose(sources, output, layout, gap, dpi, no_labels, caption):
133
+ """
134
+ Compose multiple figures into one.
135
+
136
+ \b
137
+ Examples:
138
+ scitex plt compose a.png b.png -o combined.png
139
+ scitex plt compose *.png -o fig.pdf --layout grid
140
+ scitex plt compose a.yaml b.yaml -o fig.png --gap 10
141
+ """
142
+ args = ["compose"]
143
+ args.extend(sources)
144
+ args.extend(
145
+ ["-o", output, "--layout", layout, "--gap", str(gap), "--dpi", str(dpi)]
146
+ )
147
+ if no_labels:
148
+ args.append("--no-labels")
149
+ if caption:
150
+ args.extend(["--caption", caption])
151
+ sys.exit(_run_figrecipe(*args))
152
+
153
+
154
+ @plt.command()
155
+ @click.argument("input_file", type=click.Path(exists=True))
156
+ @click.option("-o", "--output", help="Output file (default: overwrites input)")
157
+ @click.option("--margin", type=float, default=1, help="Margin to keep (mm)")
158
+ def crop(input_file, output, margin):
159
+ """
160
+ Crop whitespace from an image.
161
+
162
+ \b
163
+ Examples:
164
+ scitex plt crop figure.png
165
+ scitex plt crop figure.png -o cropped.png --margin 2
166
+ """
167
+ args = ["crop", input_file, "--margin", str(margin)]
168
+ if output:
169
+ args.extend(["-o", output])
170
+ sys.exit(_run_figrecipe(*args))
171
+
172
+
173
+ @plt.command()
174
+ @click.argument("recipe_file", type=click.Path(exists=True))
175
+ @click.option("-o", "--output", help="Output file path")
176
+ @click.option(
177
+ "--format", "fmt", type=click.Choice(["png", "pdf", "svg"]), help="Output format"
178
+ )
179
+ @click.option("--dpi", type=int, default=300, help="DPI for raster output")
180
+ def reproduce(recipe_file, output, fmt, dpi):
181
+ """
182
+ Reproduce a figure from a YAML recipe.
183
+
184
+ \b
185
+ Examples:
186
+ scitex plt reproduce recipe.yaml
187
+ scitex plt reproduce recipe.yaml -o new_fig.pdf
188
+ """
189
+ args = ["reproduce", recipe_file, "--dpi", str(dpi)]
190
+ if output:
191
+ args.extend(["-o", output])
192
+ if fmt:
193
+ args.extend(["--format", fmt])
194
+ sys.exit(_run_figrecipe(*args))
195
+
196
+
197
+ @plt.command()
198
+ @click.argument("recipe_file", type=click.Path(exists=True))
199
+ @click.option("--threshold", type=float, default=100, help="MSE threshold")
200
+ def validate(recipe_file, threshold):
201
+ """
202
+ Validate that a recipe reproduces its original figure.
203
+
204
+ \b
205
+ Examples:
206
+ scitex plt validate recipe.yaml
207
+ scitex plt validate recipe.yaml --threshold 50
208
+ """
209
+ args = ["validate", recipe_file, "--threshold", str(threshold)]
210
+ sys.exit(_run_figrecipe(*args))
211
+
212
+
213
+ @plt.command()
214
+ @click.argument("spec_file", type=click.Path(exists=True))
215
+ @click.option("-o", "--output", help="Output file path")
216
+ @click.option(
217
+ "--format", "fmt", type=click.Choice(["mermaid", "graphviz"]), default="mermaid"
218
+ )
219
+ def diagram(spec_file, output, fmt):
220
+ """
221
+ Create diagrams (flowcharts, pipelines, etc.)
222
+
223
+ \b
224
+ Examples:
225
+ scitex plt diagram workflow.yaml
226
+ scitex plt diagram pipeline.yaml -o diagram.png --format graphviz
227
+ """
228
+ args = ["diagram", spec_file, "--format", fmt]
229
+ if output:
230
+ args.extend(["-o", output])
231
+ sys.exit(_run_figrecipe(*args))
232
+
233
+
234
+ @plt.command()
235
+ @click.argument("recipe_file", type=click.Path(exists=True))
236
+ @click.option("-v", "--verbose", is_flag=True, help="Show detailed info")
237
+ def info(recipe_file, verbose):
238
+ """
239
+ Show information about a recipe.
240
+
241
+ \b
242
+ Examples:
243
+ scitex plt info recipe.yaml
244
+ scitex plt info recipe.yaml --verbose
245
+ """
246
+ args = ["info", recipe_file]
247
+ if verbose:
248
+ args.append("--verbose")
249
+ sys.exit(_run_figrecipe(*args))
250
+
251
+
252
+ @plt.command()
253
+ @click.argument("recipe_file", type=click.Path(exists=True))
254
+ @click.option("-o", "--output", help="Output file for extracted data")
255
+ def extract(recipe_file, output):
256
+ """
257
+ Extract plotted data arrays from a recipe.
258
+
259
+ \b
260
+ Examples:
261
+ scitex plt extract recipe.yaml
262
+ scitex plt extract recipe.yaml -o data.json
263
+ """
264
+ args = ["extract", recipe_file]
265
+ if output:
266
+ args.extend(["-o", output])
267
+ sys.exit(_run_figrecipe(*args))
268
+
269
+
270
+ @plt.command()
271
+ @click.argument("action", type=click.Choice(["list", "show", "set"]), default="list")
272
+ @click.argument("style_name", required=False)
273
+ def style(action, style_name):
274
+ """
275
+ Manage figure styles and presets.
276
+
277
+ \b
278
+ Examples:
279
+ scitex plt style list # List available styles
280
+ scitex plt style show nature # Show style details
281
+ scitex plt style set publication # Set default style
282
+ """
283
+ args = ["style", action]
284
+ if style_name:
285
+ args.append(style_name)
286
+ sys.exit(_run_figrecipe(*args))
287
+
288
+
289
+ @plt.command()
290
+ @click.option("--check", is_flag=True, help="Check font availability")
291
+ def fonts(check):
292
+ """
293
+ List or check available fonts.
294
+
295
+ \b
296
+ Examples:
297
+ scitex plt fonts
298
+ scitex plt fonts --check
299
+ """
300
+ args = ["fonts"]
301
+ if check:
302
+ args.append("--check")
303
+ sys.exit(_run_figrecipe(*args))
304
+
305
+
306
+ @plt.command()
307
+ @click.argument("input_file", type=click.Path(exists=True))
308
+ @click.option("-o", "--output", required=True, help="Output file path")
309
+ @click.option("--format", "fmt", help="Output format (inferred from extension)")
310
+ def convert(input_file, output, fmt):
311
+ """
312
+ Convert between figure formats.
313
+
314
+ \b
315
+ Examples:
316
+ scitex plt convert fig.png -o fig.pdf
317
+ scitex plt convert fig.svg -o fig.png
318
+ """
319
+ args = ["convert", input_file, "-o", output]
320
+ if fmt:
321
+ args.extend(["--format", fmt])
322
+ sys.exit(_run_figrecipe(*args))
323
+
324
+
325
+ @plt.group(invoke_without_command=True)
326
+ @click.pass_context
327
+ def mcp(ctx):
328
+ """
329
+ MCP (Model Context Protocol) server operations
330
+
331
+ \b
332
+ Commands:
333
+ start - Start the MCP server
334
+ doctor - Check MCP server health
335
+ list-tools - List available MCP tools
336
+ info - Show MCP server information
337
+ install - Show installation instructions
338
+
339
+ \b
340
+ Examples:
341
+ scitex plt mcp start
342
+ scitex plt mcp doctor
343
+ """
344
+ if ctx.invoked_subcommand is None:
345
+ click.echo(ctx.get_help())
346
+
347
+
348
+ @mcp.command()
349
+ @click.option("--host", default="0.0.0.0", help="Host for HTTP transport")
350
+ @click.option("--port", default=8087, type=int, help="Port for HTTP transport")
351
+ def start(host, port):
352
+ """
353
+ Start the MCP server
354
+
355
+ \b
356
+ Example:
357
+ scitex plt mcp start
358
+ scitex plt mcp start --port 8087
359
+ """
360
+ sys.exit(_run_figrecipe("mcp", "run", "--host", host, "--port", str(port)))
361
+
362
+
363
+ @mcp.command()
364
+ def doctor():
365
+ """
366
+ Check MCP server health
367
+
368
+ \b
369
+ Example:
370
+ scitex plt mcp doctor
371
+ """
372
+ sys.exit(_run_figrecipe("mcp", "doctor"))
373
+
374
+
375
+ @mcp.command("list-tools")
376
+ def list_tools_mcp():
377
+ """
378
+ List available MCP tools
379
+
380
+ \b
381
+ Example:
382
+ scitex plt mcp list-tools
383
+ """
384
+ sys.exit(_run_figrecipe("mcp", "list-tools"))
385
+
386
+
387
+ @mcp.command()
388
+ def info():
389
+ """
390
+ Show MCP server information
391
+
392
+ \b
393
+ Example:
394
+ scitex plt mcp info
395
+ """
396
+ sys.exit(_run_figrecipe("mcp", "info"))
397
+
398
+
399
+ @mcp.command()
400
+ def install():
401
+ """
402
+ Show installation instructions
403
+
404
+ \b
405
+ Example:
406
+ scitex plt mcp install
407
+ """
408
+ sys.exit(_run_figrecipe("mcp", "install"))
409
+
410
+
411
+ if __name__ == "__main__":
412
+ plt()
413
+
414
+ # EOF
scitex/cli/repro.py CHANGED
@@ -10,8 +10,13 @@ import sys
10
10
  import click
11
11
 
12
12
 
13
- @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
- def repro():
13
+ @click.group(
14
+ context_settings={"help_option_names": ["-h", "--help"]},
15
+ invoke_without_command=True,
16
+ )
17
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
18
+ @click.pass_context
19
+ def repro(ctx, help_recursive):
15
20
  """
16
21
  Reproducibility utilities
17
22
 
@@ -29,20 +34,22 @@ def repro():
29
34
  scitex repro hash data.npy # Hash array file
30
35
  scitex repro seed 42 # Set random seed
31
36
  """
32
- pass
37
+ if help_recursive:
38
+ _print_help_recursive(ctx)
39
+ ctx.exit(0)
40
+ elif ctx.invoked_subcommand is None:
41
+ click.echo(ctx.get_help())
33
42
 
34
43
 
35
- @repro.command("help-recursive")
36
- @click.pass_context
37
- def help_recursive(ctx):
38
- """Show help for all commands recursively."""
44
+ def _print_help_recursive(ctx):
45
+ """Print help for all commands recursively."""
39
46
  fake_parent = click.Context(click.Group(), info_name="scitex")
40
47
  parent_ctx = click.Context(repro, info_name="repro", parent=fake_parent)
41
48
  click.secho("━━━ scitex repro ━━━", fg="cyan", bold=True)
42
49
  click.echo(repro.get_help(parent_ctx))
43
50
  for name in sorted(repro.list_commands(ctx) or []):
44
51
  cmd = repro.get_command(ctx, name)
45
- if cmd is None or name == "help-recursive":
52
+ if cmd is None:
46
53
  continue
47
54
  click.echo()
48
55
  click.secho(f"━━━ scitex repro {name} ━━━", fg="cyan", bold=True)
scitex/cli/resource.py CHANGED
@@ -10,8 +10,13 @@ import sys
10
10
  import click
11
11
 
12
12
 
13
- @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
- def resource():
13
+ @click.group(
14
+ context_settings={"help_option_names": ["-h", "--help"]},
15
+ invoke_without_command=True,
16
+ )
17
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
18
+ @click.pass_context
19
+ def resource(ctx, help_recursive):
15
20
  """
16
21
  System resource monitoring
17
22
 
@@ -27,20 +32,22 @@ def resource():
27
32
  scitex resource usage # Current CPU/memory/GPU usage
28
33
  scitex resource monitor --interval 5
29
34
  """
30
- pass
35
+ if help_recursive:
36
+ _print_help_recursive(ctx)
37
+ ctx.exit(0)
38
+ elif ctx.invoked_subcommand is None:
39
+ click.echo(ctx.get_help())
31
40
 
32
41
 
33
- @resource.command("help-recursive")
34
- @click.pass_context
35
- def help_recursive(ctx):
36
- """Show help for all commands recursively."""
42
+ def _print_help_recursive(ctx):
43
+ """Print help for all commands recursively."""
37
44
  fake_parent = click.Context(click.Group(), info_name="scitex")
38
45
  parent_ctx = click.Context(resource, info_name="resource", parent=fake_parent)
39
46
  click.secho("━━━ scitex resource ━━━", fg="cyan", bold=True)
40
47
  click.echo(resource.get_help(parent_ctx))
41
48
  for name in sorted(resource.list_commands(ctx) or []):
42
49
  cmd = resource.get_command(ctx, name)
43
- if cmd is None or name == "help-recursive":
50
+ if cmd is None:
44
51
  continue
45
52
  click.echo()
46
53
  click.secho(f"━━━ scitex resource {name} ━━━", fg="cyan", bold=True)
@@ -15,19 +15,31 @@ Usage:
15
15
  scitex scholar config
16
16
  scitex scholar jobs list
17
17
  scitex scholar jobs status <job_id>
18
+
19
+ CrossRef database (167M+ papers via crossref-local):
20
+ scitex scholar crossref-scitex search "deep learning"
21
+ scitex scholar crossref-scitex get 10.1038/nature12373
22
+ scitex scholar crossref-scitex count "epilepsy seizure"
23
+ scitex scholar crossref-scitex info
18
24
  """
19
25
 
20
26
  from __future__ import annotations
21
27
 
22
28
  import click
23
29
 
30
+ from ._crossref_scitex import crossref_scitex
24
31
  from ._fetch import fetch
25
32
  from ._jobs import jobs
26
33
  from ._library import config, library
27
34
 
28
35
 
29
- @click.group()
30
- def scholar():
36
+ @click.group(
37
+ context_settings={"help_option_names": ["-h", "--help"]},
38
+ invoke_without_command=True,
39
+ )
40
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
41
+ @click.pass_context
42
+ def scholar(ctx, help_recursive):
31
43
  """
32
44
  Scientific paper management
33
45
 
@@ -41,13 +53,15 @@ def scholar():
41
53
  scitex scholar library
42
54
  scitex scholar jobs list
43
55
  """
44
- pass
56
+ if help_recursive:
57
+ _print_help_recursive(ctx)
58
+ ctx.exit(0)
59
+ elif ctx.invoked_subcommand is None:
60
+ click.echo(ctx.get_help())
45
61
 
46
62
 
47
- @scholar.command("help-recursive")
48
- @click.pass_context
49
- def help_recursive(ctx):
50
- """Show help for all commands recursively."""
63
+ def _print_help_recursive(ctx):
64
+ """Print help for all commands recursively."""
51
65
  fake_parent = click.Context(click.Group(), info_name="scitex")
52
66
  parent_ctx = click.Context(scholar, info_name="scholar", parent=fake_parent)
53
67
 
@@ -56,7 +70,7 @@ def help_recursive(ctx):
56
70
 
57
71
  for name in sorted(scholar.list_commands(ctx) or []):
58
72
  cmd = scholar.get_command(ctx, name)
59
- if cmd is None or name == "help-recursive":
73
+ if cmd is None:
60
74
  continue
61
75
  click.echo()
62
76
  click.secho(f"━━━ scitex scholar {name} ━━━", fg="cyan", bold=True)
@@ -79,6 +93,138 @@ def help_recursive(ctx):
79
93
  click.echo(sub_cmd.get_help(sub_sub_ctx))
80
94
 
81
95
 
96
+ @scholar.group(invoke_without_command=True)
97
+ @click.pass_context
98
+ def mcp(ctx):
99
+ """
100
+ MCP (Model Context Protocol) server operations
101
+
102
+ \b
103
+ Commands:
104
+ start - Start the MCP server
105
+ doctor - Check MCP server health
106
+ list-tools - List available MCP tools
107
+
108
+ \b
109
+ Examples:
110
+ scitex scholar mcp start
111
+ scitex scholar mcp list-tools
112
+ """
113
+ if ctx.invoked_subcommand is None:
114
+ click.echo(ctx.get_help())
115
+
116
+
117
+ @mcp.command()
118
+ @click.option(
119
+ "-t",
120
+ "--transport",
121
+ type=click.Choice(["stdio", "sse", "http"]),
122
+ default="stdio",
123
+ help="Transport protocol (default: stdio)",
124
+ )
125
+ @click.option("--host", default="0.0.0.0", help="Host for HTTP/SSE (default: 0.0.0.0)")
126
+ @click.option(
127
+ "--port", default=8097, type=int, help="Port for HTTP/SSE (default: 8097)"
128
+ )
129
+ def start(transport, host, port):
130
+ """
131
+ Start the scholar MCP server
132
+
133
+ \b
134
+ Examples:
135
+ scitex scholar mcp start
136
+ scitex scholar mcp start -t http --port 8097
137
+ """
138
+ import sys
139
+
140
+ try:
141
+ from scitex.scholar.mcp_server import main as run_server
142
+
143
+ if transport != "stdio":
144
+ click.secho(f"Starting scholar MCP server ({transport})", fg="cyan")
145
+ click.echo(f" Host: {host}")
146
+ click.echo(f" Port: {port}")
147
+
148
+ run_server()
149
+
150
+ except ImportError as e:
151
+ click.secho(f"Error: {e}", fg="red", err=True)
152
+ click.echo("\nInstall dependencies: pip install fastmcp")
153
+ sys.exit(1)
154
+ except Exception as e:
155
+ click.secho(f"Error: {e}", fg="red", err=True)
156
+ sys.exit(1)
157
+
158
+
159
+ @mcp.command()
160
+ def doctor():
161
+ """
162
+ Check MCP server health and dependencies
163
+
164
+ \b
165
+ Example:
166
+ scitex scholar mcp doctor
167
+ """
168
+ click.secho("Scholar MCP Server Health Check", fg="cyan", bold=True)
169
+ click.echo()
170
+
171
+ click.echo("Checking FastMCP... ", nl=False)
172
+ try:
173
+ import fastmcp # noqa: F401
174
+
175
+ click.secho("OK", fg="green")
176
+ except ImportError:
177
+ click.secho("NOT INSTALLED", fg="red")
178
+ click.echo(" Install with: pip install fastmcp")
179
+
180
+ click.echo("Checking scholar module... ", nl=False)
181
+ try:
182
+ from scitex import scholar as _ # noqa: F401
183
+
184
+ click.secho("OK", fg="green")
185
+ except ImportError as e:
186
+ click.secho(f"FAIL ({e})", fg="red")
187
+
188
+ click.echo("Checking crossref-local... ", nl=False)
189
+ try:
190
+ import crossref_local # noqa: F401
191
+
192
+ click.secho("OK", fg="green")
193
+ except ImportError:
194
+ click.secho("NOT INSTALLED (optional)", fg="yellow")
195
+
196
+
197
+ @mcp.command("list-tools")
198
+ def list_tools():
199
+ """
200
+ List available MCP tools
201
+
202
+ \b
203
+ Example:
204
+ scitex scholar mcp list-tools
205
+ """
206
+ click.secho("Scholar MCP Tools", fg="cyan", bold=True)
207
+ click.echo()
208
+ tools = [
209
+ ("scholar_search_papers", "Search for papers by query"),
210
+ ("scholar_resolve_dois", "Resolve DOIs to metadata"),
211
+ ("scholar_enrich_bibtex", "Enrich BibTeX with abstracts/DOIs"),
212
+ ("scholar_download_pdf", "Download PDF for a paper"),
213
+ ("scholar_download_pdfs_batch", "Batch download PDFs"),
214
+ ("scholar_get_library_status", "Get library status"),
215
+ ("scholar_parse_bibtex", "Parse BibTeX file"),
216
+ ("scholar_validate_pdfs", "Validate downloaded PDFs"),
217
+ ("scholar_authenticate", "Authenticate with institution"),
218
+ ("scholar_check_auth_status", "Check authentication status"),
219
+ ("scholar_fetch_papers", "Fetch papers by DOIs"),
220
+ ("scholar_crossref_search", "Search CrossRef database"),
221
+ ("scholar_crossref_get", "Get paper by DOI from CrossRef"),
222
+ ]
223
+ for name, desc in tools:
224
+ click.echo(f" {name}: {desc}")
225
+
226
+
227
+ scholar.add_command(crossref_scitex)
82
228
  scholar.add_command(fetch)
83
229
  scholar.add_command(library)
84
230
  scholar.add_command(config)