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
scitex/audio/_relay.py ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env python3
2
+ """Audio relay client for remote TTS playback.
3
+
4
+ Forwards speak requests to a remote audio server via HTTP.
5
+ This allows agents running on remote machines (e.g., NAS) to play
6
+ audio through the user's local speakers.
7
+
8
+ Usage:
9
+ from scitex.audio._relay import RelayClient
10
+
11
+ # Connect to local relay server
12
+ client = RelayClient("http://localhost:31293")
13
+
14
+ # Speak through relay
15
+ client.speak("Hello from remote!")
16
+
17
+ Environment Variables:
18
+ SCITEX_AUDIO_RELAY_URL: Relay server URL
19
+ SCITEX_AUDIO_RELAY_HOST: Relay server host (builds URL with port)
20
+ SCITEX_AUDIO_RELAY_PORT: Relay server port (default: 31293)
21
+ """
22
+
23
+ import json
24
+ import urllib.error
25
+ import urllib.request
26
+ from typing import Any, Dict, Optional
27
+
28
+ from ._branding import DEFAULT_PORT, get_relay_url
29
+
30
+
31
+ class RelayClient:
32
+ """HTTP client for audio relay server.
33
+
34
+ Forwards TTS requests to a remote server running the audio MCP
35
+ in HTTP mode. Enables remote agents to play audio locally.
36
+
37
+ Example:
38
+ >>> client = RelayClient("http://localhost:31293")
39
+ >>> client.speak("Hello!")
40
+ {'success': True, 'text': 'Hello!', ...}
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ base_url: Optional[str] = None,
46
+ timeout: int = 30,
47
+ ):
48
+ """Initialize relay client.
49
+
50
+ Args:
51
+ base_url: Relay server URL. Auto-detects from env if None.
52
+ timeout: Request timeout in seconds.
53
+ """
54
+ self.base_url = (
55
+ base_url or get_relay_url() or f"http://localhost:{DEFAULT_PORT}"
56
+ ).rstrip("/")
57
+ self.timeout = timeout
58
+
59
+ def _request(
60
+ self,
61
+ endpoint: str,
62
+ data: Optional[Dict[str, Any]] = None,
63
+ method: str = "POST",
64
+ ) -> Dict[str, Any]:
65
+ """Make HTTP request to relay server."""
66
+ url = f"{self.base_url}{endpoint}"
67
+
68
+ try:
69
+ if data is not None:
70
+ req_data = json.dumps(data).encode("utf-8")
71
+ req = urllib.request.Request(url, data=req_data, method=method)
72
+ req.add_header("Content-Type", "application/json")
73
+ else:
74
+ req = urllib.request.Request(url, method=method)
75
+
76
+ req.add_header("Accept", "application/json")
77
+
78
+ with urllib.request.urlopen(req, timeout=self.timeout) as response:
79
+ return json.loads(response.read().decode("utf-8"))
80
+
81
+ except urllib.error.HTTPError as e:
82
+ raise ConnectionError(f"Relay request failed: {e.code} {e.reason}") from e
83
+ except urllib.error.URLError as e:
84
+ raise ConnectionError(
85
+ f"Cannot connect to relay at {self.base_url}: {e.reason}"
86
+ ) from e
87
+
88
+ def health(self) -> Dict[str, Any]:
89
+ """Check relay server health."""
90
+ try:
91
+ # Try MCP health endpoint
92
+ return self._request("/health", method="GET")
93
+ except Exception:
94
+ # Relay may not have /health, try simple request
95
+ return {"status": "unknown", "url": self.base_url}
96
+
97
+ def is_available(self) -> bool:
98
+ """Check if relay server is reachable."""
99
+ try:
100
+ self.health()
101
+ return True
102
+ except Exception:
103
+ return False
104
+
105
+ def speak(
106
+ self,
107
+ text: str,
108
+ backend: Optional[str] = None,
109
+ voice: Optional[str] = None,
110
+ rate: int = 150,
111
+ speed: float = 1.5,
112
+ play: bool = True,
113
+ save: bool = False,
114
+ fallback: bool = True,
115
+ agent_id: Optional[str] = None,
116
+ ) -> Dict[str, Any]:
117
+ """Forward speak request to relay server.
118
+
119
+ Args:
120
+ text: Text to speak
121
+ backend: TTS backend (auto if None)
122
+ voice: Voice/language
123
+ rate: Speech rate (pyttsx3)
124
+ speed: Speed multiplier (gtts)
125
+ play: Play audio
126
+ save: Save audio file
127
+ fallback: Try fallback backends
128
+ agent_id: Agent identifier
129
+
130
+ Returns
131
+ -------
132
+ Response dict with success status
133
+ """
134
+ data = {
135
+ "text": text,
136
+ "backend": backend,
137
+ "voice": voice,
138
+ "rate": rate,
139
+ "speed": speed,
140
+ "play": play,
141
+ "save": save,
142
+ "fallback": fallback,
143
+ "agent_id": agent_id,
144
+ }
145
+
146
+ # Remove None values
147
+ data = {k: v for k, v in data.items() if v is not None}
148
+
149
+ return self._request("/speak", data)
150
+
151
+ def list_backends(self) -> Dict[str, Any]:
152
+ """Get available backends from relay server."""
153
+ return self._request("/list_backends", method="GET")
154
+
155
+
156
+ # Module-level client singleton
157
+ _client: Optional[RelayClient] = None
158
+
159
+
160
+ def get_relay_client(base_url: Optional[str] = None) -> RelayClient:
161
+ """Get or create relay client singleton."""
162
+ global _client
163
+ if _client is None or (base_url and _client.base_url != base_url):
164
+ _client = RelayClient(base_url)
165
+ return _client
166
+
167
+
168
+ def reset_relay_client() -> None:
169
+ """Reset relay client singleton."""
170
+ global _client
171
+ _client = None
172
+
173
+
174
+ def relay_speak(
175
+ text: str,
176
+ backend: Optional[str] = None,
177
+ voice: Optional[str] = None,
178
+ rate: int = 150,
179
+ speed: float = 1.5,
180
+ play: bool = True,
181
+ **kwargs,
182
+ ) -> Dict[str, Any]:
183
+ """Convenience function to speak via relay.
184
+
185
+ Args:
186
+ text: Text to speak
187
+ backend: TTS backend
188
+ voice: Voice/language
189
+ rate: Speech rate
190
+ speed: Speed multiplier
191
+ play: Play audio
192
+ **kwargs: Additional options
193
+
194
+ Returns
195
+ -------
196
+ Response dict
197
+ """
198
+ client = get_relay_client()
199
+ return client.speak(
200
+ text=text,
201
+ backend=backend,
202
+ voice=voice,
203
+ rate=rate,
204
+ speed=speed,
205
+ play=play,
206
+ **kwargs,
207
+ )
208
+
209
+
210
+ def is_relay_available() -> bool:
211
+ """Check if relay server is available."""
212
+ try:
213
+ client = get_relay_client()
214
+ return client.is_available()
215
+ except Exception:
216
+ return False
217
+
218
+
219
+ __all__ = [
220
+ "RelayClient",
221
+ "get_relay_client",
222
+ "reset_relay_client",
223
+ "relay_speak",
224
+ "is_relay_available",
225
+ ]
@@ -38,6 +38,10 @@ class ElevenLabsTTS(BaseTTS):
38
38
  "sam": "yoZ06aMxZJJ28mfd3POQ",
39
39
  }
40
40
 
41
+ # ElevenLabs API speed limits
42
+ MIN_SPEED = 0.7
43
+ MAX_SPEED = 1.2
44
+
41
45
  def __init__(
42
46
  self,
43
47
  api_key: Optional[str] = None,
@@ -58,7 +62,8 @@ class ElevenLabsTTS(BaseTTS):
58
62
  self.model_id = model_id
59
63
  self.stability = stability
60
64
  self.similarity_boost = similarity_boost
61
- self.speed = speed
65
+ # Clamp speed to ElevenLabs API limits (0.7-1.2)
66
+ self.speed = max(self.MIN_SPEED, min(self.MAX_SPEED, speed))
62
67
  self._client = None
63
68
 
64
69
  @property
@@ -10,13 +10,13 @@ Enables remote agents to connect and play audio on local speakers.
10
10
 
11
11
  Usage:
12
12
  scitex audio serve # stdio (default)
13
- scitex audio serve -t http --port 8084 # HTTP transport
14
- scitex audio serve -t sse --port 8084 # SSE transport
13
+ scitex audio serve -t http --port 31293 # HTTP transport
14
+ scitex audio serve -t sse --port 31293 # SSE transport
15
15
 
16
16
  For remote audio:
17
- 1. Run locally: scitex audio serve -t http --port 8084
18
- 2. SSH tunnel: ssh -R 8084:localhost:8084 remote-host
19
- 3. Remote agent connects to http://localhost:8084
17
+ 1. Run locally: scitex audio serve -t http --port 31293
18
+ 2. SSH tunnel: ssh -R 31293:localhost:31293 remote-host
19
+ 3. Remote agent connects to http://localhost:31293
20
20
  """
21
21
 
22
22
  from __future__ import annotations
@@ -35,16 +35,19 @@ except ImportError:
35
35
  FASTMCP_AVAILABLE = False
36
36
  FastMCP = None # type: ignore
37
37
 
38
- __all__ = ["mcp", "run_server", "main", "FASTMCP_AVAILABLE"]
38
+ __all__ = ["mcp", "run_server", "run_relay_server", "main", "FASTMCP_AVAILABLE"]
39
+
40
+ # Import branding
41
+ from ._branding import (
42
+ get_mcp_instructions,
43
+ get_mcp_server_name,
44
+ )
39
45
 
40
46
  # Initialize MCP server
41
47
  if FASTMCP_AVAILABLE:
42
48
  mcp = FastMCP(
43
- name="scitex-audio",
44
- instructions=(
45
- "Text-to-Speech server with multiple backends (pyttsx3, gtts, elevenlabs). "
46
- "Use speak() to convert text to speech. Supports cross-process FIFO queuing."
47
- ),
49
+ name=get_mcp_server_name(),
50
+ instructions=get_mcp_instructions(),
48
51
  )
49
52
  else:
50
53
  mcp = None
@@ -61,6 +64,16 @@ def _get_audio_dir() -> Path:
61
64
 
62
65
 
63
66
  if FASTMCP_AVAILABLE:
67
+ import asyncio
68
+
69
+ def _run_async(coro):
70
+ """Run async handler synchronously for FastMCP tools."""
71
+ try:
72
+ loop = asyncio.get_event_loop()
73
+ except RuntimeError:
74
+ loop = asyncio.new_event_loop()
75
+ asyncio.set_event_loop(loop)
76
+ return loop.run_until_complete(coro)
64
77
 
65
78
  @mcp.tool()
66
79
  def speak(
@@ -74,72 +87,92 @@ if FASTMCP_AVAILABLE:
74
87
  fallback: bool = True,
75
88
  agent_id: Optional[str] = None,
76
89
  ) -> str:
77
- """Convert text to speech with fallback (pyttsx3 -> gtts -> elevenlabs).
90
+ """[audio] Convert text to speech with fallback (pyttsx3 -> gtts -> elevenlabs).
78
91
 
79
- Args:
80
- text: Text to convert to speech
81
- backend: TTS backend (auto-selects with fallback if not specified)
82
- voice: Voice/language (gtts: 'en','fr'; elevenlabs: 'rachel','adam')
83
- rate: Speech rate in words per minute (pyttsx3 only, default 150)
84
- speed: Speed multiplier for gtts (1.0=normal, 1.5=faster, 0.7=slower)
85
- play: Play audio after generation (default True)
86
- save: Save audio to file (default False)
87
- fallback: Try next backend on failure (default True)
88
- agent_id: Optional identifier for the agent making the request
92
+ NOTE: Plays on SERVER. For remote use, see speak_relay.
93
+ """
94
+ return audio_speak_local(
95
+ text=text, backend=backend, voice=voice, rate=rate,
96
+ speed=speed, play=play, save=save, fallback=fallback,
97
+ agent_id=agent_id,
98
+ )
89
99
 
90
- Returns
91
- -------
92
- JSON string with success status and details
100
+ @mcp.tool()
101
+ def audio_speak_local(
102
+ text: str,
103
+ backend: Optional[str] = None,
104
+ voice: Optional[str] = None,
105
+ rate: int = 150,
106
+ speed: float = 1.5,
107
+ play: bool = True,
108
+ save: bool = False,
109
+ fallback: bool = True,
110
+ agent_id: Optional[str] = None,
111
+ ) -> str:
112
+ """[audio] Convert text to speech on the LOCAL/SERVER machine.
93
113
 
94
- Examples
95
- --------
96
- speak("Hello world")
97
- speak("Bonjour", backend="gtts", voice="fr")
98
- speak("Fast speech", rate=200)
99
- """
100
- from . import speak as tts_speak
101
- from ._cross_process_lock import AudioPlaybackLock
114
+ Use when running Claude Code directly on your local machine.
115
+ Audio plays where MCP server runs.
102
116
 
103
- output_path = None
104
- if save:
105
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
106
- output_path = str(_get_audio_dir() / f"tts_{timestamp}.mp3")
117
+ Args:
118
+ text: Text to convert to speech
119
+ backend: TTS backend (pyttsx3, gtts, elevenlabs)
120
+ voice: Voice/language
121
+ rate: Speech rate (pyttsx3 only)
122
+ speed: Speed multiplier (gtts)
123
+ play: Play audio (default True)
124
+ save: Save to file (default False)
125
+ fallback: Try next backend on failure
126
+ agent_id: Agent identifier
127
+ """
128
+ from ._mcp.speak_handlers import speak_local_handler
107
129
 
108
- try:
109
- # Acquire cross-process lock to ensure FIFO across all instances
110
- lock = AudioPlaybackLock()
111
- lock.acquire(timeout=120.0)
112
- try:
113
- tts_speak(
114
- text=text,
115
- backend=backend,
116
- voice=voice,
117
- play=play,
118
- output_path=output_path,
119
- fallback=fallback,
120
- rate=rate,
121
- speed=speed,
122
- )
123
- finally:
124
- lock.release()
130
+ result = _run_async(speak_local_handler(
131
+ text=text, backend=backend, voice=voice, rate=rate,
132
+ speed=speed, play=play, save=save, fallback=fallback,
133
+ agent_id=agent_id,
134
+ ))
135
+ return json.dumps(result, indent=2)
125
136
 
126
- response = {
127
- "success": True,
128
- "text": text,
129
- "backend": backend,
130
- "voice": voice,
131
- "played": play,
132
- "agent_id": agent_id,
133
- "timestamp": datetime.now().isoformat(),
134
- }
137
+ @mcp.tool()
138
+ def audio_speak_relay(
139
+ text: str,
140
+ backend: Optional[str] = None,
141
+ voice: Optional[str] = None,
142
+ rate: int = 150,
143
+ speed: float = 1.5,
144
+ play: bool = True,
145
+ save: bool = False,
146
+ fallback: bool = True,
147
+ agent_id: Optional[str] = None,
148
+ ) -> str:
149
+ """[audio] Convert text to speech via RELAY server (remote playback).
135
150
 
136
- if output_path:
137
- response["saved_to"] = output_path
151
+ Use when running on remote server (e.g., NAS) and want audio
152
+ on your local machine. REQUIRES relay server running locally.
138
153
 
139
- return json.dumps(response, indent=2)
154
+ Args:
155
+ text: Text to convert to speech
156
+ backend: TTS backend (pyttsx3, gtts, elevenlabs)
157
+ voice: Voice/language
158
+ rate: Speech rate (pyttsx3 only)
159
+ speed: Speed multiplier (gtts)
160
+ play: Play audio (default True)
161
+ save: Save to file (default False)
162
+ fallback: Try next backend on failure
163
+ agent_id: Agent identifier
164
+
165
+ Returns:
166
+ Success with playback info, or error with setup instructions
167
+ """
168
+ from ._mcp.speak_handlers import speak_relay_handler
140
169
 
141
- except Exception as e:
142
- return json.dumps({"success": False, "error": str(e)}, indent=2)
170
+ result = _run_async(speak_relay_handler(
171
+ text=text, backend=backend, voice=voice, rate=rate,
172
+ speed=speed, play=play, save=save, fallback=fallback,
173
+ agent_id=agent_id,
174
+ ))
175
+ return json.dumps(result, indent=2)
143
176
 
144
177
  @mcp.tool()
145
178
  def list_backends() -> str:
@@ -244,21 +277,28 @@ if FASTMCP_AVAILABLE:
244
277
 
245
278
  def run_server(
246
279
  transport: str = "stdio",
247
- host: str = "0.0.0.0",
248
- port: int = 8084,
280
+ host: Optional[str] = None,
281
+ port: Optional[int] = None,
249
282
  ) -> None:
250
283
  """Run the MCP server.
251
284
 
252
285
  Args:
253
286
  transport: Transport protocol ("stdio", "sse", or "http")
254
- host: Host for HTTP/SSE transport
255
- port: Port for HTTP/SSE transport
287
+ host: Host for HTTP/SSE transport (default from branding)
288
+ port: Port for HTTP/SSE transport (default from branding)
256
289
  """
290
+ from ._branding import DEFAULT_HOST, DEFAULT_PORT
291
+
292
+ host = host or DEFAULT_HOST
293
+ port = port or DEFAULT_PORT
294
+
257
295
  if not FASTMCP_AVAILABLE:
258
296
  import sys
259
297
 
298
+ from ._branding import BRAND_NAME
299
+
260
300
  print("=" * 60)
261
- print("MCP Server 'scitex-audio' requires the 'fastmcp' package.")
301
+ print(f"MCP Server '{BRAND_NAME}' requires the 'fastmcp' package.")
262
302
  print()
263
303
  print("Install with:")
264
304
  print(" pip install fastmcp")
@@ -268,19 +308,132 @@ def run_server(
268
308
  print("=" * 60)
269
309
  sys.exit(1)
270
310
 
311
+ from ._branding import BRAND_NAME
312
+
271
313
  if transport == "stdio":
272
314
  mcp.run(transport="stdio")
273
315
  elif transport == "sse":
274
- print(f"Starting scitex-audio MCP server (SSE) on {host}:{port}")
316
+ print(f"Starting {BRAND_NAME} MCP server (SSE) on {host}:{port}")
275
317
  mcp.run(transport="sse", host=host, port=port)
276
318
  elif transport == "http":
277
- print(f"Starting scitex-audio MCP server (HTTP) on {host}:{port}")
319
+ print(f"Starting {BRAND_NAME} MCP server (HTTP) on {host}:{port}")
278
320
  print(f"Connect via: http://{host}:{port}/mcp")
279
321
  mcp.run(transport="streamable-http", host=host, port=port)
280
322
  else:
281
323
  raise ValueError(f"Unknown transport: {transport}")
282
324
 
283
325
 
326
+ def run_relay_server(host: Optional[str] = None, port: Optional[int] = None) -> None:
327
+ """Run HTTP relay server for remote audio playback.
328
+
329
+ This exposes simple REST endpoints that remote agents can connect to.
330
+ Unlike the MCP server, this uses standard HTTP POST/GET.
331
+
332
+ Endpoints:
333
+ POST /speak - Speak text
334
+ GET /health - Health check
335
+ GET /list_backends - List available backends
336
+ """
337
+ from ._branding import BRAND_NAME, DEFAULT_HOST, DEFAULT_PORT
338
+
339
+ host = host or DEFAULT_HOST
340
+ port = port or DEFAULT_PORT
341
+
342
+ try:
343
+ from http.server import BaseHTTPRequestHandler, HTTPServer
344
+ except ImportError as e:
345
+ raise RuntimeError(f"HTTP server not available: {e}") from e
346
+
347
+ class RelayHandler(BaseHTTPRequestHandler):
348
+ """HTTP handler for audio relay requests."""
349
+
350
+ def _send_json(self, data: dict, status: int = 200) -> None:
351
+ """Send JSON response."""
352
+ import json
353
+
354
+ body = json.dumps(data, indent=2).encode("utf-8")
355
+ self.send_response(status)
356
+ self.send_header("Content-Type", "application/json")
357
+ self.send_header("Content-Length", str(len(body)))
358
+ self.send_header("Access-Control-Allow-Origin", "*")
359
+ self.end_headers()
360
+ self.wfile.write(body)
361
+
362
+ def do_OPTIONS(self) -> None:
363
+ """Handle CORS preflight."""
364
+ self.send_response(200)
365
+ self.send_header("Access-Control-Allow-Origin", "*")
366
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
367
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
368
+ self.end_headers()
369
+
370
+ def do_GET(self) -> None:
371
+ """Handle GET requests."""
372
+ import json
373
+
374
+ if self.path == "/health":
375
+ self._send_json({"status": "healthy", "server": BRAND_NAME})
376
+ elif self.path == "/list_backends":
377
+ result = list_backends()
378
+ self._send_json(json.loads(result))
379
+ else:
380
+ self._send_json({"error": "Not found"}, 404)
381
+
382
+ def do_POST(self) -> None:
383
+ """Handle POST requests."""
384
+ import json
385
+
386
+ if self.path == "/speak":
387
+ content_length = int(self.headers.get("Content-Length", 0))
388
+ body = self.rfile.read(content_length)
389
+ try:
390
+ data = json.loads(body.decode("utf-8"))
391
+ # Import speak directly from audio module (not MCP tool)
392
+ from . import speak as tts_speak
393
+ from ._cross_process_lock import AudioPlaybackLock
394
+
395
+ # Acquire lock for FIFO
396
+ lock = AudioPlaybackLock()
397
+ lock.acquire(timeout=120.0)
398
+ try:
399
+ tts_speak(
400
+ text=data.get("text", ""),
401
+ backend=data.get("backend"),
402
+ voice=data.get("voice"),
403
+ rate=data.get("rate", 150),
404
+ speed=data.get("speed", 1.5),
405
+ play=data.get("play", True),
406
+ fallback=data.get("fallback", True),
407
+ mode="local",
408
+ )
409
+ finally:
410
+ lock.release()
411
+
412
+ self._send_json({
413
+ "success": True,
414
+ "text": data.get("text", ""),
415
+ "played": True,
416
+ "timestamp": datetime.now().isoformat(),
417
+ })
418
+ except Exception as e:
419
+ self._send_json({"success": False, "error": str(e)}, 500)
420
+ else:
421
+ self._send_json({"error": "Not found"}, 404)
422
+
423
+ def log_message(self, format: str, *args) -> None:
424
+ """Suppress default logging."""
425
+ pass
426
+
427
+ print(f"Starting {BRAND_NAME} relay server on {host}:{port}")
428
+ print("Endpoints: POST /speak, GET /health, GET /list_backends")
429
+ server = HTTPServer((host, port), RelayHandler)
430
+ try:
431
+ server.serve_forever()
432
+ except KeyboardInterrupt:
433
+ print("\nShutting down relay server")
434
+ server.shutdown()
435
+
436
+
284
437
  def main():
285
438
  """Entry point for scitex-audio-mcp command."""
286
439
  run_server(transport="stdio")
scitex/canvas/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- ---
2
2
  !-- Timestamp: 2025-12-08
3
3
  !-- Author: ywatanabe
4
- !-- File: /home/ywatanabe/proj/scitex-code/src/scitex/vis/README.md
4
+ !-- File: /home/ywatanabe/proj/scitex-python/src/scitex/vis/README.md
5
5
  !-- --- -->
6
6
 
7
7
  # scitex.vis - Canvas-Based Figure Composition
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/__init__.py
4
+
5
+ """
6
+ DearPyGui editor package.
7
+
8
+ Modular GPU-accelerated figure editor using DearPyGui.
9
+
10
+ Modules:
11
+ - _state: EditorState dataclass for all editor state
12
+ - _utils: Utility functions (checkerboard, constants)
13
+ - _rendering: Figure rendering and highlight drawing
14
+ - _plotting: CSV data plotting
15
+ - _selection: Element selection and hover detection
16
+ - _handlers: Event handlers and callbacks
17
+ - _panels: UI panel creation
18
+ """
19
+
20
+ from ._editor import DearPyGuiEditor
21
+
22
+ __all__ = ["DearPyGuiEditor"]
23
+
24
+
25
+ # EOF