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
@@ -1,35 +1,20 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # File: ./src/scitex/vis/editor/flask_editor/templates/__init__.py
4
3
  """Template components for Flask editor.
5
4
 
6
- Supports two modes:
7
- - Static files mode (default): Uses external CSS/JS files from static/
8
- - Inline mode (fallback): Embeds CSS/JS directly in HTML
5
+ Uses external CSS/JS files from static/ directory.
9
6
  """
10
7
 
11
8
  from ._html import HTML_BODY
12
9
 
13
- # Configuration flag - set to False to use inline mode for debugging
14
- USE_STATIC_FILES = True
15
10
 
16
-
17
- def build_html_template(use_static: bool = None) -> str:
18
- """Build the complete HTML template from components.
19
-
20
- Args:
21
- use_static: Override static file usage. If None, uses USE_STATIC_FILES.
11
+ def build_html_template() -> str:
12
+ """Build the complete HTML template.
22
13
 
23
14
  Returns:
24
15
  Complete HTML template string.
25
16
  """
26
- if use_static is None:
27
- use_static = USE_STATIC_FILES
28
-
29
- if use_static:
30
- return _build_static_template()
31
- else:
32
- return _build_inline_template()
17
+ return _build_static_template()
33
18
 
34
19
 
35
20
  def _build_static_template() -> str:
@@ -37,42 +22,44 @@ def _build_static_template() -> str:
37
22
  # Get list of JS files in correct load order
38
23
  js_files = [
39
24
  # Dev tools (load first to capture console logs)
40
- 'js/dev/element-inspector.js',
25
+ "js/dev/element-inspector.js",
41
26
  # Core modules first (dependencies)
42
- 'js/core/state.js',
43
- 'js/core/utils.js',
44
- 'js/core/api.js',
27
+ "js/core/state.js",
28
+ "js/core/utils.js",
29
+ "js/core/api.js",
45
30
  # Editor modules
46
- 'js/editor/bbox.js',
47
- 'js/editor/overlay.js',
48
- 'js/editor/preview.js',
49
- 'js/editor/element-drag.js',
31
+ "js/editor/bbox.js",
32
+ "js/editor/overlay.js",
33
+ "js/editor/preview.js",
34
+ "js/editor/element-drag.js",
50
35
  # Canvas modules
51
- 'js/canvas/selection.js',
52
- 'js/canvas/resize.js',
53
- 'js/canvas/dragging.js',
54
- 'js/canvas/canvas.js',
36
+ "js/canvas/selection.js",
37
+ "js/canvas/resize.js",
38
+ "js/canvas/dragging.js",
39
+ "js/canvas/canvas.js",
55
40
  # Alignment modules
56
- 'js/alignment/basic.js',
57
- 'js/alignment/axis.js',
58
- 'js/alignment/distribute.js',
41
+ "js/alignment/basic.js",
42
+ "js/alignment/axis.js",
43
+ "js/alignment/distribute.js",
59
44
  # UI modules
60
- 'js/ui/theme.js',
61
- 'js/ui/help.js',
62
- 'js/ui/download.js',
63
- 'js/ui/controls.js',
45
+ "js/ui/theme.js",
46
+ "js/ui/help.js",
47
+ "js/ui/download.js",
48
+ "js/ui/controls.js",
64
49
  # Shortcuts
65
- 'js/shortcuts/context-menu.js',
66
- 'js/shortcuts/keyboard.js',
50
+ "js/shortcuts/context-menu.js",
51
+ "js/shortcuts/keyboard.js",
67
52
  # Main entry (last)
68
- 'js/main.js',
53
+ "js/main.js",
69
54
  ]
70
55
 
71
56
  # Generate script tags
72
- script_tags = '\n '.join([
73
- f'<script src="{{{{ url_for(\'static\', filename=\'{f}\') }}}}"></script>'
74
- for f in js_files
75
- ])
57
+ script_tags = "\n ".join(
58
+ [
59
+ f"<script src=\"{{{{ url_for('static', filename='{f}') }}}}\"></script>"
60
+ for f in js_files
61
+ ]
62
+ )
76
63
 
77
64
  return f"""<!DOCTYPE html>
78
65
  <html lang="en" data-theme="dark">
@@ -95,29 +82,4 @@ def _build_static_template() -> str:
95
82
  """
96
83
 
97
84
 
98
- def _build_inline_template() -> str:
99
- """Build template with inline CSS/JS (fallback mode)."""
100
- from ._styles import CSS_STYLES
101
- from ._scripts import JS_SCRIPTS
102
-
103
- return f"""<!DOCTYPE html>
104
- <html lang="en" data-theme="dark">
105
- <head>
106
- <meta charset="UTF-8">
107
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
- <title>SciTeX Figure Editor - {{{{ filename }}}}</title>
109
- <style>
110
- {CSS_STYLES}
111
- </style>
112
- </head>
113
- <body>
114
- {HTML_BODY}
115
- <script>
116
- {JS_SCRIPTS}
117
- </script>
118
- </body>
119
- </html>
120
- """
121
-
122
-
123
85
  # EOF
scitex/cli/__init__.py CHANGED
@@ -16,49 +16,44 @@ import click
16
16
  from .main import cli
17
17
 
18
18
 
19
- def add_help_recursive(group: click.Group, parent_name: str = "scitex") -> None:
20
- """Add help-recursive command to a click group.
19
+ def print_help_recursive(ctx, group: click.Group) -> None:
20
+ """Print help for a group and all its subcommands.
21
21
 
22
22
  Args:
23
- group: The click group to add the command to
24
- parent_name: The parent command name for display (e.g., "scitex writer")
23
+ ctx: The click context
24
+ group: The click group to print help for
25
25
  """
26
-
27
- @group.command("help-recursive")
28
- @click.pass_context
29
- def help_recursive(ctx):
30
- """Show help for all commands recursively."""
31
- # Create a fake parent context to show correct command path in Usage
32
- fake_parent = click.Context(click.Group(), info_name="scitex")
33
- parent_ctx = click.Context(group, info_name=group.name, parent=fake_parent)
34
-
35
- click.secho(f"━━━ scitex {group.name} ━━━", fg="cyan", bold=True)
36
- click.echo(group.get_help(parent_ctx))
37
-
38
- for name in sorted(group.list_commands(ctx) or []):
39
- cmd = group.get_command(ctx, name)
40
- if cmd is None or name == "help-recursive":
41
- continue
42
- click.echo()
43
- click.secho(f"━━━ scitex {group.name} {name} ━━━", fg="cyan", bold=True)
44
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
45
- click.echo(cmd.get_help(sub_ctx))
46
- # Handle nested subgroups
47
- if isinstance(cmd, click.Group):
48
- for sub_name in sorted(cmd.list_commands(sub_ctx) or []):
49
- sub_cmd = cmd.get_command(sub_ctx, sub_name)
50
- if sub_cmd is None:
51
- continue
52
- click.echo()
53
- click.secho(
54
- f"━━━ scitex {group.name} {name} {sub_name} ━━━",
55
- fg="cyan",
56
- bold=True,
57
- )
58
- with click.Context(
59
- sub_cmd, info_name=sub_name, parent=sub_ctx
60
- ) as sub_sub_ctx:
61
- click.echo(sub_cmd.get_help(sub_sub_ctx))
62
-
63
-
64
- __all__ = ["cli", "add_help_recursive"]
26
+ # Create a fake parent context to show correct command path in Usage
27
+ fake_parent = click.Context(click.Group(), info_name="scitex")
28
+ parent_ctx = click.Context(group, info_name=group.name, parent=fake_parent)
29
+
30
+ click.secho(f"━━━ scitex {group.name} ━━━", fg="cyan", bold=True)
31
+ click.echo(group.get_help(parent_ctx))
32
+
33
+ for name in sorted(group.list_commands(ctx) or []):
34
+ cmd = group.get_command(ctx, name)
35
+ if cmd is None:
36
+ continue
37
+ click.echo()
38
+ click.secho(f"━━━ scitex {group.name} {name} ━━━", fg="cyan", bold=True)
39
+ with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
40
+ click.echo(cmd.get_help(sub_ctx))
41
+ # Handle nested subgroups
42
+ if isinstance(cmd, click.Group):
43
+ for sub_name in sorted(cmd.list_commands(sub_ctx) or []):
44
+ sub_cmd = cmd.get_command(sub_ctx, sub_name)
45
+ if sub_cmd is None:
46
+ continue
47
+ click.echo()
48
+ click.secho(
49
+ f"━━━ scitex {group.name} {name} {sub_name} ━━━",
50
+ fg="cyan",
51
+ bold=True,
52
+ )
53
+ with click.Context(
54
+ sub_cmd, info_name=sub_name, parent=sub_ctx
55
+ ) as sub_sub_ctx:
56
+ click.echo(sub_cmd.get_help(sub_sub_ctx))
57
+
58
+
59
+ __all__ = ["cli", "print_help_recursive"]
scitex/cli/audio.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 audio():
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 audio(ctx, help_recursive):
15
20
  """
16
21
  Text-to-speech utilities
17
22
 
@@ -28,25 +33,13 @@ def audio():
28
33
  scitex audio backends # List available backends
29
34
  scitex audio check # Check audio status (WSL)
30
35
  """
31
- pass
32
-
36
+ if help_recursive:
37
+ from . import print_help_recursive
33
38
 
34
- @audio.command("help-recursive")
35
- @click.pass_context
36
- def help_recursive(ctx):
37
- """Show help for all commands recursively."""
38
- fake_parent = click.Context(click.Group(), info_name="scitex")
39
- parent_ctx = click.Context(audio, info_name="audio", parent=fake_parent)
40
- click.secho("━━━ scitex audio ━━━", fg="cyan", bold=True)
41
- click.echo(audio.get_help(parent_ctx))
42
- for name in sorted(audio.list_commands(ctx) or []):
43
- cmd = audio.get_command(ctx, name)
44
- if cmd is None or name == "help-recursive":
45
- continue
46
- click.echo()
47
- click.secho(f"━━━ scitex audio {name} ━━━", fg="cyan", bold=True)
48
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
49
- click.echo(cmd.get_help(sub_ctx))
39
+ print_help_recursive(ctx, audio)
40
+ ctx.exit(0)
41
+ elif ctx.invoked_subcommand is None:
42
+ click.echo(ctx.get_help())
50
43
 
51
44
 
52
45
  @audio.command()
@@ -247,7 +240,28 @@ def stop():
247
240
  sys.exit(1)
248
241
 
249
242
 
250
- @audio.command()
243
+ @audio.group(invoke_without_command=True)
244
+ @click.pass_context
245
+ def mcp(ctx):
246
+ """
247
+ MCP (Model Context Protocol) server operations
248
+
249
+ \b
250
+ Commands:
251
+ start - Start the MCP server
252
+ doctor - Check MCP server health
253
+ list-tools - List available MCP tools
254
+
255
+ \b
256
+ Examples:
257
+ scitex audio mcp start
258
+ scitex audio mcp start -t http --port 31293
259
+ """
260
+ if ctx.invoked_subcommand is None:
261
+ click.echo(ctx.get_help())
262
+
263
+
264
+ @mcp.command()
251
265
  @click.option(
252
266
  "-t",
253
267
  "--transport",
@@ -262,13 +276,13 @@ def stop():
262
276
  )
263
277
  @click.option(
264
278
  "--port",
265
- default=8084,
279
+ default=31293,
266
280
  type=int,
267
- help="Port for HTTP/SSE transport (default: 8084)",
281
+ help="Port for HTTP/SSE transport (default: 31293)",
268
282
  )
269
- def serve(transport, host, port):
283
+ def start(transport, host, port):
270
284
  """
271
- Run MCP server for remote audio playback
285
+ Start the MCP server for remote audio playback
272
286
 
273
287
  Enables remote agents (via SSH) to play audio on local speakers.
274
288
 
@@ -280,22 +294,9 @@ def serve(transport, host, port):
280
294
 
281
295
  \b
282
296
  Examples:
283
- # Local stdio (Claude Desktop)
284
- scitex audio serve
285
-
286
- # HTTP server for remote agents
287
- scitex audio serve -t http --port 8084
288
-
289
- # SSE server
290
- scitex audio serve -t sse --port 8084
291
-
292
- \b
293
- Remote Setup:
294
- 1. Local: scitex audio serve -t http --port 8084
295
- 2. SSH: Add to ~/.ssh/config:
296
- LocalForward 8084 127.0.0.1:8084
297
- 3. Remote MCP config:
298
- {"type": "sse", "url": "http://localhost:8084/sse"}
297
+ scitex audio mcp start
298
+ scitex audio mcp start -t http --port 31293
299
+ scitex audio mcp start -t sse --port 31293
299
300
  """
300
301
  try:
301
302
  from scitex.audio.mcp_server import FASTMCP_AVAILABLE, run_server
@@ -327,5 +328,123 @@ def serve(transport, host, port):
327
328
  sys.exit(1)
328
329
 
329
330
 
331
+ @mcp.command()
332
+ def doctor():
333
+ """
334
+ Check MCP server health and dependencies
335
+
336
+ \b
337
+ Example:
338
+ scitex audio mcp doctor
339
+ """
340
+ click.secho("Audio MCP Server Health Check", fg="cyan", bold=True)
341
+ click.echo()
342
+
343
+ # Check fastmcp
344
+ click.echo("Checking FastMCP... ", nl=False)
345
+ try:
346
+ from scitex.audio.mcp_server import FASTMCP_AVAILABLE
347
+
348
+ if FASTMCP_AVAILABLE:
349
+ click.secho("OK", fg="green")
350
+ else:
351
+ click.secho("NOT INSTALLED", fg="red")
352
+ click.echo(" Install with: pip install fastmcp")
353
+ except ImportError:
354
+ click.secho("FAIL", fg="red")
355
+
356
+ # Check audio backends
357
+ click.echo("Checking audio backends... ", nl=False)
358
+ try:
359
+ from scitex.audio import available_backends
360
+
361
+ backends = available_backends()
362
+ if backends:
363
+ click.secho(f"OK ({', '.join(backends)})", fg="green")
364
+ else:
365
+ click.secho("NONE AVAILABLE", fg="yellow")
366
+ except Exception as e:
367
+ click.secho(f"FAIL ({e})", fg="red")
368
+
369
+
370
+ @mcp.command("list-tools")
371
+ def list_tools():
372
+ """
373
+ List available MCP tools
374
+
375
+ \b
376
+ Example:
377
+ scitex audio mcp list-tools
378
+ """
379
+ click.secho("Audio MCP Tools", fg="cyan", bold=True)
380
+ click.echo()
381
+ tools = [
382
+ ("audio_speak", "Convert text to speech and play audio"),
383
+ ("audio_generate_audio", "Generate audio file without playing"),
384
+ ("audio_list_backends", "List available TTS backends"),
385
+ ("audio_list_voices", "List available voices for a backend"),
386
+ ("audio_play_audio", "Play an audio file"),
387
+ ("audio_check_audio_status", "Check audio system status"),
388
+ ]
389
+ for name, desc in tools:
390
+ click.echo(f" {name}: {desc}")
391
+
392
+
393
+ @audio.command()
394
+ @click.option(
395
+ "--host",
396
+ default="0.0.0.0",
397
+ help="Host to bind (default: 0.0.0.0)",
398
+ )
399
+ @click.option(
400
+ "--port",
401
+ default=31293,
402
+ type=int,
403
+ help="Port to bind (default: 31293)",
404
+ )
405
+ def relay(host, port):
406
+ """
407
+ Run simple HTTP relay server for remote audio playback
408
+
409
+ Unlike 'serve' (MCP server), this exposes simple REST endpoints
410
+ that remote agents can POST to for audio playback.
411
+
412
+ \b
413
+ Endpoints:
414
+ POST /speak - Play text-to-speech
415
+ GET /health - Health check
416
+ GET /list_backends - List available backends
417
+
418
+ \b
419
+ Example:
420
+ # On your local machine (where you want audio)
421
+ scitex audio relay --port 31293
422
+
423
+ # On remote server, set env var
424
+ export SCITEX_AUDIO_RELAY_URL=http://YOUR_LOCAL_IP:31293
425
+
426
+ # Or use SSH reverse tunnel
427
+ ssh -R 31293:localhost:31293 remote-server
428
+ """
429
+ try:
430
+ from scitex.audio.mcp_server import run_relay_server
431
+
432
+ click.secho("Starting audio relay server", fg="cyan")
433
+ click.echo(f" Host: {host}")
434
+ click.echo(f" Port: {port}")
435
+ click.echo()
436
+ click.echo("Endpoints:")
437
+ click.echo(" POST /speak - Play text-to-speech")
438
+ click.echo(" GET /health - Health check")
439
+ click.echo(" GET /list_backends - List backends")
440
+ click.echo()
441
+
442
+ run_relay_server(host=host, port=port)
443
+
444
+ except Exception as e:
445
+ click.secho(f"Error: {e}", fg="red", err=True)
446
+ sys.exit(1)
447
+
448
+
330
449
  if __name__ == "__main__":
331
450
  audio()
scitex/cli/capture.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 capture():
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 capture(ctx, help_recursive):
15
20
  """
16
21
  Screen capture and monitoring utilities
17
22
 
@@ -33,25 +38,13 @@ def capture():
33
38
  scitex capture gif # Create GIF from latest session
34
39
  scitex capture info # List monitors and windows
35
40
  """
36
- pass
41
+ if help_recursive:
42
+ from . import print_help_recursive
37
43
 
38
-
39
- @capture.command("help-recursive")
40
- @click.pass_context
41
- def help_recursive(ctx):
42
- """Show help for all commands recursively."""
43
- fake_parent = click.Context(click.Group(), info_name="scitex")
44
- parent_ctx = click.Context(capture, info_name="capture", parent=fake_parent)
45
- click.secho("━━━ scitex capture ━━━", fg="cyan", bold=True)
46
- click.echo(capture.get_help(parent_ctx))
47
- for name in sorted(capture.list_commands(ctx) or []):
48
- cmd = capture.get_command(ctx, name)
49
- if cmd is None or name == "help-recursive":
50
- continue
51
- click.echo()
52
- click.secho(f"━━━ scitex capture {name} ━━━", fg="cyan", bold=True)
53
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
54
- click.echo(cmd.get_help(sub_ctx))
44
+ print_help_recursive(ctx, capture)
45
+ ctx.exit(0)
46
+ elif ctx.invoked_subcommand is None:
47
+ click.echo(ctx.get_help())
55
48
 
56
49
 
57
50
  @capture.command()
@@ -321,5 +314,125 @@ def window(handle, output, quality):
321
314
  sys.exit(1)
322
315
 
323
316
 
317
+ @capture.group(invoke_without_command=True)
318
+ @click.pass_context
319
+ def mcp(ctx):
320
+ """
321
+ MCP (Model Context Protocol) server operations
322
+
323
+ \b
324
+ Commands:
325
+ start - Start the MCP server
326
+ doctor - Check MCP server health
327
+ list-tools - List available MCP tools
328
+
329
+ \b
330
+ Examples:
331
+ scitex capture mcp start
332
+ scitex capture mcp list-tools
333
+ """
334
+ if ctx.invoked_subcommand is None:
335
+ click.echo(ctx.get_help())
336
+
337
+
338
+ @mcp.command("start")
339
+ @click.option(
340
+ "-t",
341
+ "--transport",
342
+ type=click.Choice(["stdio", "sse", "http"]),
343
+ default="stdio",
344
+ help="Transport protocol (default: stdio)",
345
+ )
346
+ @click.option("--host", default="0.0.0.0", help="Host for HTTP/SSE (default: 0.0.0.0)")
347
+ @click.option(
348
+ "--port", default=8096, type=int, help="Port for HTTP/SSE (default: 8096)"
349
+ )
350
+ def mcp_start(transport, host, port):
351
+ """
352
+ Start the capture MCP server
353
+
354
+ \b
355
+ Examples:
356
+ scitex capture mcp start
357
+ scitex capture mcp start -t http --port 8096
358
+ """
359
+ try:
360
+ from scitex.capture.mcp_server import main as run_server
361
+
362
+ if transport != "stdio":
363
+ click.secho(f"Starting capture MCP server ({transport})", fg="cyan")
364
+ click.echo(f" Host: {host}")
365
+ click.echo(f" Port: {port}")
366
+
367
+ run_server()
368
+
369
+ except ImportError as e:
370
+ click.secho(f"Error: {e}", fg="red", err=True)
371
+ click.echo("\nInstall dependencies: pip install fastmcp")
372
+ sys.exit(1)
373
+ except Exception as e:
374
+ click.secho(f"Error: {e}", fg="red", err=True)
375
+ sys.exit(1)
376
+
377
+
378
+ @mcp.command()
379
+ def doctor():
380
+ """
381
+ Check MCP server health and dependencies
382
+
383
+ \b
384
+ Example:
385
+ scitex capture mcp doctor
386
+ """
387
+ click.secho("Capture MCP Server Health Check", fg="cyan", bold=True)
388
+ click.echo()
389
+
390
+ click.echo("Checking FastMCP... ", nl=False)
391
+ try:
392
+ import fastmcp # noqa: F401
393
+
394
+ click.secho("OK", fg="green")
395
+ except ImportError:
396
+ click.secho("NOT INSTALLED", fg="red")
397
+ click.echo(" Install with: pip install fastmcp")
398
+
399
+ click.echo("Checking capture module... ", nl=False)
400
+ try:
401
+ from scitex import capture as _ # noqa: F401
402
+
403
+ click.secho("OK", fg="green")
404
+ except ImportError as e:
405
+ click.secho(f"FAIL ({e})", fg="red")
406
+
407
+
408
+ @mcp.command("list-tools")
409
+ def list_tools():
410
+ """
411
+ List available MCP tools
412
+
413
+ \b
414
+ Example:
415
+ scitex capture mcp list-tools
416
+ """
417
+ click.secho("Capture MCP Tools", fg="cyan", bold=True)
418
+ click.echo()
419
+ tools = [
420
+ ("capture_capture_screenshot", "Capture screenshot"),
421
+ ("capture_capture_window", "Capture specific window"),
422
+ ("capture_start_monitoring", "Start continuous capture"),
423
+ ("capture_stop_monitoring", "Stop monitoring"),
424
+ ("capture_get_monitoring_status", "Get monitoring status"),
425
+ ("capture_analyze_screenshot", "Analyze screenshot for errors"),
426
+ ("capture_list_recent_screenshots", "List recent screenshots"),
427
+ ("capture_clear_cache", "Clear screenshot cache"),
428
+ ("capture_create_gif", "Create animated GIF"),
429
+ ("capture_list_sessions", "List monitoring sessions"),
430
+ ("capture_get_info", "Get monitor/window info"),
431
+ ("capture_list_windows", "List visible windows"),
432
+ ]
433
+ for name, desc in tools:
434
+ click.echo(f" {name}: {desc}")
435
+
436
+
324
437
  if __name__ == "__main__":
325
438
  capture()