scitex 2.7.3__py3-none-any.whl → 2.10.0__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.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dev/plt/plot_mpl_axhline.py +0 -0
- scitex/dev/plt/plot_mpl_axhspan.py +0 -0
- scitex/dev/plt/plot_mpl_axvline.py +0 -0
- scitex/dev/plt/plot_mpl_axvspan.py +0 -0
- scitex/dev/plt/plot_mpl_bar.py +0 -0
- scitex/dev/plt/plot_mpl_barh.py +0 -0
- scitex/dev/plt/plot_mpl_boxplot.py +0 -0
- scitex/dev/plt/plot_mpl_contour.py +0 -0
- scitex/dev/plt/plot_mpl_contourf.py +0 -0
- scitex/dev/plt/plot_mpl_errorbar.py +0 -0
- scitex/dev/plt/plot_mpl_eventplot.py +0 -0
- scitex/dev/plt/plot_mpl_fill.py +0 -0
- scitex/dev/plt/plot_mpl_fill_between.py +0 -0
- scitex/dev/plt/plot_mpl_hexbin.py +0 -0
- scitex/dev/plt/plot_mpl_hist.py +0 -0
- scitex/dev/plt/plot_mpl_hist2d.py +0 -0
- scitex/dev/plt/plot_mpl_imshow.py +0 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
- scitex/dev/plt/plot_mpl_pie.py +0 -0
- scitex/dev/plt/plot_mpl_plot.py +0 -0
- scitex/dev/plt/plot_mpl_quiver.py +0 -0
- scitex/dev/plt/plot_mpl_scatter.py +0 -0
- scitex/dev/plt/plot_mpl_stackplot.py +0 -0
- scitex/dev/plt/plot_mpl_stem.py +0 -0
- scitex/dev/plt/plot_mpl_step.py +0 -0
- scitex/dev/plt/plot_mpl_violinplot.py +0 -0
- scitex/dev/plt/plot_sns_barplot.py +0 -0
- scitex/dev/plt/plot_sns_boxplot.py +0 -0
- scitex/dev/plt/plot_sns_heatmap.py +0 -0
- scitex/dev/plt/plot_sns_histplot.py +0 -0
- scitex/dev/plt/plot_sns_kdeplot.py +0 -0
- scitex/dev/plt/plot_sns_lineplot.py +0 -0
- scitex/dev/plt/plot_sns_scatterplot.py +0 -0
- scitex/dev/plt/plot_sns_stripplot.py +0 -0
- scitex/dev/plt/plot_sns_swarmplot.py +0 -0
- scitex/dev/plt/plot_sns_violinplot.py +0 -0
- scitex/dev/plt/plot_stx_bar.py +0 -0
- scitex/dev/plt/plot_stx_barh.py +0 -0
- scitex/dev/plt/plot_stx_box.py +0 -0
- scitex/dev/plt/plot_stx_boxplot.py +0 -0
- scitex/dev/plt/plot_stx_conf_mat.py +0 -0
- scitex/dev/plt/plot_stx_contour.py +0 -0
- scitex/dev/plt/plot_stx_ecdf.py +0 -0
- scitex/dev/plt/plot_stx_errorbar.py +0 -0
- scitex/dev/plt/plot_stx_fill_between.py +0 -0
- scitex/dev/plt/plot_stx_fillv.py +0 -0
- scitex/dev/plt/plot_stx_heatmap.py +0 -0
- scitex/dev/plt/plot_stx_image.py +0 -0
- scitex/dev/plt/plot_stx_imshow.py +0 -0
- scitex/dev/plt/plot_stx_joyplot.py +0 -0
- scitex/dev/plt/plot_stx_kde.py +0 -0
- scitex/dev/plt/plot_stx_line.py +0 -0
- scitex/dev/plt/plot_stx_mean_ci.py +0 -0
- scitex/dev/plt/plot_stx_mean_std.py +0 -0
- scitex/dev/plt/plot_stx_median_iqr.py +0 -0
- scitex/dev/plt/plot_stx_raster.py +0 -0
- scitex/dev/plt/plot_stx_rectangle.py +0 -0
- scitex/dev/plt/plot_stx_scatter.py +0 -0
- scitex/dev/plt/plot_stx_shaded_line.py +0 -0
- scitex/dev/plt/plot_stx_violin.py +0 -0
- scitex/dev/plt/plot_stx_violinplot.py +0 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/__init__.py +5 -2
- scitex/fig/editor/_dearpygui_editor.py +1 -1
- scitex/fig/editor/_mpl_editor.py +1 -1
- scitex/fig/editor/_qt_editor.py +1 -1
- scitex/fig/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +23 -0
- scitex/fig/editor/flask_editor/_core.py +908 -103
- scitex/fig/editor/flask_editor/_renderer.py +74 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
- scitex/fig/editor/flask_editor/templates/_html.py +27 -9
- scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
- scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
- scitex/fig/io/_bundle.py +104 -19
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -20
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +219 -89
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/bundle/_zip.py +487 -0
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/io/_layered_bundle.py +0 -0
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_plot.py +0 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
- scitex/fig/editor/_edit.py +0 -751
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
scitex/audio/mcp_server.py
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
#
|
|
3
|
-
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
2
|
+
# Timestamp: "2025-12-27 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/mcp_server.py
|
|
5
4
|
# ----------------------------------------
|
|
6
5
|
|
|
7
6
|
"""
|
|
8
7
|
MCP Server for SciTeX Audio - Text-to-Speech with Multiple Backends
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Backends:
|
|
13
|
-
- pyttsx3: System TTS (offline, free)
|
|
14
|
-
- gtts: Google TTS (free, requires internet)
|
|
15
|
-
- elevenlabs: ElevenLabs (paid, high quality)
|
|
9
|
+
Uses cross-process FIFO locking to ensure only one instance
|
|
10
|
+
plays audio at a time across all Claude Code sessions.
|
|
16
11
|
"""
|
|
17
12
|
|
|
18
13
|
from __future__ import annotations
|
|
@@ -24,7 +19,6 @@ import uuid
|
|
|
24
19
|
from dataclasses import dataclass, field
|
|
25
20
|
from datetime import datetime
|
|
26
21
|
from pathlib import Path
|
|
27
|
-
from typing import Any, Optional
|
|
28
22
|
|
|
29
23
|
import mcp.types as types
|
|
30
24
|
from mcp.server import NotificationOptions, Server
|
|
@@ -40,16 +34,17 @@ class SpeechRequest:
|
|
|
40
34
|
|
|
41
35
|
request_id: str
|
|
42
36
|
text: str
|
|
43
|
-
backend:
|
|
44
|
-
voice:
|
|
45
|
-
rate:
|
|
46
|
-
speed:
|
|
37
|
+
backend: str | None = None
|
|
38
|
+
voice: str | None = None
|
|
39
|
+
rate: int | None = None
|
|
40
|
+
speed: float | None = None
|
|
47
41
|
play: bool = True
|
|
48
42
|
save: bool = False
|
|
49
43
|
fallback: bool = True
|
|
50
44
|
future: asyncio.Future = field(default_factory=lambda: None)
|
|
51
45
|
created_at: datetime = field(default_factory=datetime.now)
|
|
52
|
-
agent_id:
|
|
46
|
+
agent_id: str | None = None
|
|
47
|
+
|
|
53
48
|
|
|
54
49
|
# Directory configuration
|
|
55
50
|
SCITEX_BASE_DIR = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
@@ -58,24 +53,18 @@ SCITEX_AUDIO_DIR = SCITEX_BASE_DIR / "audio"
|
|
|
58
53
|
|
|
59
54
|
def get_audio_dir() -> Path:
|
|
60
55
|
"""Get the audio output directory."""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return audio_dir
|
|
56
|
+
SCITEX_AUDIO_DIR.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
return SCITEX_AUDIO_DIR
|
|
64
58
|
|
|
65
59
|
|
|
66
60
|
class AudioServer:
|
|
67
|
-
"""MCP Server for Text-to-Speech with
|
|
68
|
-
|
|
69
|
-
Features a sequential speech queue to prevent audio overlap when
|
|
70
|
-
multiple agents request speech simultaneously.
|
|
71
|
-
"""
|
|
61
|
+
"""MCP Server for Text-to-Speech with cross-process FIFO queuing."""
|
|
72
62
|
|
|
73
63
|
def __init__(self):
|
|
74
64
|
self.server = Server("scitex-audio")
|
|
75
|
-
# Speech queue for sequential processing
|
|
76
65
|
self._speech_queue: asyncio.Queue[SpeechRequest] = asyncio.Queue()
|
|
77
|
-
self._queue_processor_task:
|
|
78
|
-
self._current_request:
|
|
66
|
+
self._queue_processor_task: asyncio.Task | None = None
|
|
67
|
+
self._current_request: SpeechRequest | None = None
|
|
79
68
|
self._processed_count: int = 0
|
|
80
69
|
self._is_processing: bool = False
|
|
81
70
|
self.setup_handlers()
|
|
@@ -91,19 +80,15 @@ class AudioServer:
|
|
|
91
80
|
"""Process speech requests sequentially from the queue."""
|
|
92
81
|
while True:
|
|
93
82
|
try:
|
|
94
|
-
# Wait for next request
|
|
95
83
|
request = await self._speech_queue.get()
|
|
96
84
|
self._current_request = request
|
|
97
85
|
self._is_processing = True
|
|
98
86
|
|
|
99
87
|
try:
|
|
100
|
-
# Execute the speech request
|
|
101
88
|
result = await self._execute_speak(request)
|
|
102
|
-
# Set the result on the future if it exists
|
|
103
89
|
if request.future and not request.future.done():
|
|
104
90
|
request.future.set_result(result)
|
|
105
91
|
except Exception as e:
|
|
106
|
-
# Set exception on future if it exists
|
|
107
92
|
if request.future and not request.future.done():
|
|
108
93
|
request.future.set_exception(e)
|
|
109
94
|
finally:
|
|
@@ -115,35 +100,40 @@ class AudioServer:
|
|
|
115
100
|
except asyncio.CancelledError:
|
|
116
101
|
break
|
|
117
102
|
except Exception:
|
|
118
|
-
# Continue processing even if one request fails
|
|
119
103
|
continue
|
|
120
104
|
|
|
121
105
|
async def _execute_speak(self, request: SpeechRequest) -> dict:
|
|
122
|
-
"""Execute a single speech request."""
|
|
106
|
+
"""Execute a single speech request with cross-process locking."""
|
|
123
107
|
from . import available_backends
|
|
124
108
|
from . import speak as tts_speak
|
|
109
|
+
from ._cross_process_lock import AudioPlaybackLock
|
|
125
110
|
|
|
126
111
|
loop = asyncio.get_event_loop()
|
|
127
112
|
|
|
128
|
-
# Determine output path
|
|
129
113
|
output_path = None
|
|
130
114
|
if request.save:
|
|
131
115
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
132
116
|
output_path = str(get_audio_dir() / f"tts_{timestamp}.mp3")
|
|
133
117
|
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
118
|
+
def do_speak_with_lock():
|
|
119
|
+
# Acquire cross-process lock to ensure FIFO across all instances
|
|
120
|
+
lock = AudioPlaybackLock()
|
|
121
|
+
lock.acquire(timeout=120.0)
|
|
122
|
+
try:
|
|
123
|
+
return tts_speak(
|
|
124
|
+
text=request.text,
|
|
125
|
+
backend=request.backend,
|
|
126
|
+
voice=request.voice,
|
|
127
|
+
play=request.play,
|
|
128
|
+
output_path=output_path,
|
|
129
|
+
fallback=request.fallback,
|
|
130
|
+
rate=request.rate,
|
|
131
|
+
speed=request.speed,
|
|
132
|
+
)
|
|
133
|
+
finally:
|
|
134
|
+
lock.release()
|
|
145
135
|
|
|
146
|
-
|
|
136
|
+
await loop.run_in_executor(None, do_speak_with_lock)
|
|
147
137
|
|
|
148
138
|
backends = available_backends()
|
|
149
139
|
used_backend = request.backend or (backends[0] if backends else None)
|
|
@@ -167,199 +157,48 @@ class AudioServer:
|
|
|
167
157
|
return response
|
|
168
158
|
|
|
169
159
|
def setup_handlers(self):
|
|
160
|
+
"""Set up MCP server handlers."""
|
|
161
|
+
from ._mcp_handlers import (
|
|
162
|
+
check_audio_status_handler,
|
|
163
|
+
clear_audio_cache_handler,
|
|
164
|
+
generate_audio_handler,
|
|
165
|
+
list_audio_files_handler,
|
|
166
|
+
list_backends_handler,
|
|
167
|
+
list_voices_handler,
|
|
168
|
+
play_audio_handler,
|
|
169
|
+
)
|
|
170
|
+
from ._mcp_tool_schemas import get_tool_schemas
|
|
171
|
+
|
|
170
172
|
@self.server.list_tools()
|
|
171
173
|
async def handle_list_tools():
|
|
172
|
-
return
|
|
173
|
-
types.Tool(
|
|
174
|
-
name="speak",
|
|
175
|
-
description="Convert text to speech with fallback (pyttsx3 -> gtts -> elevenlabs). Requests are queued for sequential playback to prevent audio overlap.",
|
|
176
|
-
inputSchema={
|
|
177
|
-
"type": "object",
|
|
178
|
-
"properties": {
|
|
179
|
-
"text": {
|
|
180
|
-
"type": "string",
|
|
181
|
-
"description": "Text to convert to speech",
|
|
182
|
-
},
|
|
183
|
-
"backend": {
|
|
184
|
-
"type": "string",
|
|
185
|
-
"description": "TTS backend (auto-selects with fallback if not specified)",
|
|
186
|
-
"enum": ["pyttsx3", "gtts", "elevenlabs"],
|
|
187
|
-
},
|
|
188
|
-
"voice": {
|
|
189
|
-
"type": "string",
|
|
190
|
-
"description": "Voice/language (gtts: 'en','fr'; elevenlabs: 'rachel','adam')",
|
|
191
|
-
},
|
|
192
|
-
"rate": {
|
|
193
|
-
"type": "integer",
|
|
194
|
-
"description": "Speech rate in words per minute (pyttsx3 only, default 150, faster=200+)",
|
|
195
|
-
"default": 150,
|
|
196
|
-
},
|
|
197
|
-
"speed": {
|
|
198
|
-
"type": "number",
|
|
199
|
-
"description": "Speed multiplier for gtts (1.0=normal, 1.5=faster, 0.7=slower)",
|
|
200
|
-
"default": 1.5,
|
|
201
|
-
},
|
|
202
|
-
"play": {
|
|
203
|
-
"type": "boolean",
|
|
204
|
-
"description": "Play audio after generation",
|
|
205
|
-
"default": True,
|
|
206
|
-
},
|
|
207
|
-
"save": {
|
|
208
|
-
"type": "boolean",
|
|
209
|
-
"description": "Save audio to file",
|
|
210
|
-
"default": False,
|
|
211
|
-
},
|
|
212
|
-
"fallback": {
|
|
213
|
-
"type": "boolean",
|
|
214
|
-
"description": "Try next backend on failure",
|
|
215
|
-
"default": True,
|
|
216
|
-
},
|
|
217
|
-
"agent_id": {
|
|
218
|
-
"type": "string",
|
|
219
|
-
"description": "Optional identifier for the agent making the request",
|
|
220
|
-
},
|
|
221
|
-
"wait": {
|
|
222
|
-
"type": "boolean",
|
|
223
|
-
"description": "Wait for speech to complete before returning (default: True)",
|
|
224
|
-
"default": True,
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
"required": ["text"],
|
|
228
|
-
},
|
|
229
|
-
),
|
|
230
|
-
types.Tool(
|
|
231
|
-
name="generate_audio",
|
|
232
|
-
description="Generate speech audio file without playing",
|
|
233
|
-
inputSchema={
|
|
234
|
-
"type": "object",
|
|
235
|
-
"properties": {
|
|
236
|
-
"text": {
|
|
237
|
-
"type": "string",
|
|
238
|
-
"description": "Text to convert to speech",
|
|
239
|
-
},
|
|
240
|
-
"backend": {
|
|
241
|
-
"type": "string",
|
|
242
|
-
"description": "TTS backend",
|
|
243
|
-
"enum": ["gtts", "elevenlabs", "pyttsx3"],
|
|
244
|
-
"default": "gtts",
|
|
245
|
-
},
|
|
246
|
-
"voice": {
|
|
247
|
-
"type": "string",
|
|
248
|
-
"description": "Voice/language",
|
|
249
|
-
},
|
|
250
|
-
"output_path": {
|
|
251
|
-
"type": "string",
|
|
252
|
-
"description": "Output file path",
|
|
253
|
-
},
|
|
254
|
-
"return_base64": {
|
|
255
|
-
"type": "boolean",
|
|
256
|
-
"description": "Return audio as base64",
|
|
257
|
-
"default": False,
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
"required": ["text"],
|
|
261
|
-
},
|
|
262
|
-
),
|
|
263
|
-
types.Tool(
|
|
264
|
-
name="list_backends",
|
|
265
|
-
description="List available TTS backends and their status",
|
|
266
|
-
inputSchema={"type": "object", "properties": {}},
|
|
267
|
-
),
|
|
268
|
-
types.Tool(
|
|
269
|
-
name="list_voices",
|
|
270
|
-
description="List available voices for a backend",
|
|
271
|
-
inputSchema={
|
|
272
|
-
"type": "object",
|
|
273
|
-
"properties": {
|
|
274
|
-
"backend": {
|
|
275
|
-
"type": "string",
|
|
276
|
-
"description": "TTS backend",
|
|
277
|
-
"enum": ["gtts", "elevenlabs", "pyttsx3"],
|
|
278
|
-
"default": "gtts",
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
),
|
|
283
|
-
types.Tool(
|
|
284
|
-
name="play_audio",
|
|
285
|
-
description="Play an audio file",
|
|
286
|
-
inputSchema={
|
|
287
|
-
"type": "object",
|
|
288
|
-
"properties": {
|
|
289
|
-
"path": {
|
|
290
|
-
"type": "string",
|
|
291
|
-
"description": "Path to audio file",
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
"required": ["path"],
|
|
295
|
-
},
|
|
296
|
-
),
|
|
297
|
-
types.Tool(
|
|
298
|
-
name="list_audio_files",
|
|
299
|
-
description="List generated audio files",
|
|
300
|
-
inputSchema={
|
|
301
|
-
"type": "object",
|
|
302
|
-
"properties": {
|
|
303
|
-
"limit": {
|
|
304
|
-
"type": "integer",
|
|
305
|
-
"description": "Maximum files to list",
|
|
306
|
-
"default": 20,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
),
|
|
311
|
-
types.Tool(
|
|
312
|
-
name="clear_audio_cache",
|
|
313
|
-
description="Clear generated audio files",
|
|
314
|
-
inputSchema={
|
|
315
|
-
"type": "object",
|
|
316
|
-
"properties": {
|
|
317
|
-
"max_age_hours": {
|
|
318
|
-
"type": "number",
|
|
319
|
-
"description": "Delete files older than N hours (0 = all)",
|
|
320
|
-
"default": 24,
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
),
|
|
325
|
-
types.Tool(
|
|
326
|
-
name="speech_queue_status",
|
|
327
|
-
description="Get the current speech queue status (pending requests, currently playing, etc.)",
|
|
328
|
-
inputSchema={"type": "object", "properties": {}},
|
|
329
|
-
),
|
|
330
|
-
types.Tool(
|
|
331
|
-
name="check_audio_status",
|
|
332
|
-
description="Check WSL audio connectivity and available playback methods",
|
|
333
|
-
inputSchema={"type": "object", "properties": {}},
|
|
334
|
-
),
|
|
335
|
-
]
|
|
174
|
+
return get_tool_schemas()
|
|
336
175
|
|
|
337
176
|
@self.server.call_tool()
|
|
338
177
|
async def handle_call_tool(name: str, arguments: dict):
|
|
339
|
-
# Ensure queue processor is running for speak requests
|
|
340
178
|
if name == "speak":
|
|
341
179
|
await self.start_queue_processor()
|
|
342
180
|
return await self.speak(**arguments)
|
|
343
181
|
elif name == "generate_audio":
|
|
344
|
-
return await
|
|
182
|
+
return await generate_audio_handler(**arguments)
|
|
345
183
|
elif name == "list_backends":
|
|
346
|
-
return await
|
|
184
|
+
return await list_backends_handler()
|
|
347
185
|
elif name == "list_voices":
|
|
348
|
-
return await
|
|
186
|
+
return await list_voices_handler(**arguments)
|
|
349
187
|
elif name == "play_audio":
|
|
350
|
-
return await
|
|
188
|
+
return await play_audio_handler(**arguments)
|
|
351
189
|
elif name == "list_audio_files":
|
|
352
|
-
return await
|
|
190
|
+
return await list_audio_files_handler(**arguments)
|
|
353
191
|
elif name == "clear_audio_cache":
|
|
354
|
-
return await
|
|
192
|
+
return await clear_audio_cache_handler(**arguments)
|
|
355
193
|
elif name == "speech_queue_status":
|
|
356
194
|
return await self.speech_queue_status()
|
|
357
195
|
elif name == "check_audio_status":
|
|
358
|
-
return await
|
|
196
|
+
return await check_audio_status_handler()
|
|
197
|
+
elif name == "announce_context":
|
|
198
|
+
return await self.announce_context(**arguments)
|
|
359
199
|
else:
|
|
360
200
|
raise ValueError(f"Unknown tool: {name}")
|
|
361
201
|
|
|
362
|
-
# Provide audio files as resources
|
|
363
202
|
@self.server.list_resources()
|
|
364
203
|
async def handle_list_resources():
|
|
365
204
|
audio_dir = get_audio_dir()
|
|
@@ -375,9 +214,7 @@ class AudioServer:
|
|
|
375
214
|
|
|
376
215
|
for audio_file in audio_files:
|
|
377
216
|
mtime = datetime.fromtimestamp(audio_file.stat().st_mtime)
|
|
378
|
-
mime_type =
|
|
379
|
-
"audio/mpeg" if audio_file.suffix == ".mp3" else "audio/wav"
|
|
380
|
-
)
|
|
217
|
+
mime_type = "audio/mpeg" if audio_file.suffix == ".mp3" else "audio/wav"
|
|
381
218
|
resources.append(
|
|
382
219
|
types.Resource(
|
|
383
220
|
uri=f"audio://{audio_file.name}",
|
|
@@ -410,42 +247,22 @@ class AudioServer:
|
|
|
410
247
|
async def speak(
|
|
411
248
|
self,
|
|
412
249
|
text: str,
|
|
413
|
-
backend:
|
|
414
|
-
voice:
|
|
415
|
-
rate:
|
|
416
|
-
speed:
|
|
250
|
+
backend: str | None = None,
|
|
251
|
+
voice: str | None = None,
|
|
252
|
+
rate: int | None = None,
|
|
253
|
+
speed: float | None = None,
|
|
417
254
|
play: bool = True,
|
|
418
255
|
save: bool = False,
|
|
419
256
|
fallback: bool = True,
|
|
420
|
-
agent_id:
|
|
257
|
+
agent_id: str | None = None,
|
|
421
258
|
wait: bool = True,
|
|
422
259
|
):
|
|
423
|
-
"""
|
|
424
|
-
|
|
425
|
-
Requests are queued for sequential playback to prevent audio overlap
|
|
426
|
-
when multiple agents request speech simultaneously.
|
|
427
|
-
|
|
428
|
-
Args:
|
|
429
|
-
text: Text to convert to speech
|
|
430
|
-
backend: TTS backend to use
|
|
431
|
-
voice: Voice/language selection
|
|
432
|
-
rate: Speech rate (pyttsx3 only)
|
|
433
|
-
speed: Speed multiplier (gtts only)
|
|
434
|
-
play: Whether to play audio after generation
|
|
435
|
-
save: Whether to save audio to file
|
|
436
|
-
fallback: Whether to try next backend on failure
|
|
437
|
-
agent_id: Optional identifier for the requesting agent
|
|
438
|
-
wait: Whether to wait for speech to complete (default: True)
|
|
439
|
-
"""
|
|
260
|
+
"""Queue speech request with cross-process FIFO ordering."""
|
|
440
261
|
try:
|
|
441
|
-
# Create a unique request ID
|
|
442
262
|
request_id = str(uuid.uuid4())[:8]
|
|
443
|
-
|
|
444
|
-
# Create a future to wait for the result
|
|
445
263
|
loop = asyncio.get_event_loop()
|
|
446
264
|
future = loop.create_future()
|
|
447
265
|
|
|
448
|
-
# Create the speech request
|
|
449
266
|
request = SpeechRequest(
|
|
450
267
|
request_id=request_id,
|
|
451
268
|
text=text,
|
|
@@ -460,18 +277,14 @@ class AudioServer:
|
|
|
460
277
|
agent_id=agent_id,
|
|
461
278
|
)
|
|
462
279
|
|
|
463
|
-
# Add to queue
|
|
464
280
|
await self._speech_queue.put(request)
|
|
465
|
-
|
|
466
281
|
queue_position = self._speech_queue.qsize()
|
|
467
282
|
|
|
468
283
|
if wait:
|
|
469
|
-
# Wait for the speech to complete
|
|
470
284
|
result = await future
|
|
471
|
-
result["queue_position"] = 0
|
|
285
|
+
result["queue_position"] = 0
|
|
472
286
|
return result
|
|
473
287
|
else:
|
|
474
|
-
# Return immediately with queue info
|
|
475
288
|
return {
|
|
476
289
|
"success": True,
|
|
477
290
|
"queued": True,
|
|
@@ -513,225 +326,52 @@ class AudioServer:
|
|
|
513
326
|
except Exception as e:
|
|
514
327
|
return {"success": False, "error": str(e)}
|
|
515
328
|
|
|
516
|
-
async def
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
backend: Optional[str] = None,
|
|
520
|
-
voice: Optional[str] = None,
|
|
521
|
-
output_path: Optional[str] = None,
|
|
522
|
-
return_base64: bool = False,
|
|
523
|
-
):
|
|
524
|
-
"""Generate audio file without playing."""
|
|
525
|
-
try:
|
|
526
|
-
from . import speak as tts_speak, available_backends
|
|
527
|
-
|
|
528
|
-
loop = asyncio.get_event_loop()
|
|
529
|
-
|
|
530
|
-
# Determine output path
|
|
531
|
-
if not output_path:
|
|
532
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
533
|
-
output_path = str(get_audio_dir() / f"tts_{timestamp}.mp3")
|
|
534
|
-
|
|
535
|
-
def do_generate():
|
|
536
|
-
return tts_speak(
|
|
537
|
-
text=text,
|
|
538
|
-
backend=backend,
|
|
539
|
-
voice=voice,
|
|
540
|
-
play=False,
|
|
541
|
-
output_path=output_path,
|
|
542
|
-
fallback=True,
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
result_path = await loop.run_in_executor(None, do_generate)
|
|
546
|
-
|
|
547
|
-
result = {
|
|
548
|
-
"success": True,
|
|
549
|
-
"path": str(result_path),
|
|
550
|
-
"text": text,
|
|
551
|
-
"backend": backend,
|
|
552
|
-
"timestamp": datetime.now().isoformat(),
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
# Get file size
|
|
556
|
-
if result_path.exists():
|
|
557
|
-
result["size_kb"] = round(result_path.stat().st_size / 1024, 2)
|
|
558
|
-
|
|
559
|
-
if return_base64 and result_path.exists():
|
|
560
|
-
with open(result_path, "rb") as f:
|
|
561
|
-
result["base64"] = base64.b64encode(f.read()).decode()
|
|
562
|
-
|
|
563
|
-
return result
|
|
564
|
-
|
|
565
|
-
except Exception as e:
|
|
566
|
-
return {"success": False, "error": str(e)}
|
|
567
|
-
|
|
568
|
-
async def list_backends(self):
|
|
569
|
-
"""List available TTS backends."""
|
|
570
|
-
try:
|
|
571
|
-
from . import available_backends
|
|
572
|
-
|
|
573
|
-
backends = available_backends()
|
|
574
|
-
|
|
575
|
-
info = []
|
|
576
|
-
for b in ["gtts", "elevenlabs", "pyttsx3"]:
|
|
577
|
-
available = b in backends
|
|
578
|
-
desc = {
|
|
579
|
-
"gtts": "Google TTS - Free, requires internet",
|
|
580
|
-
"elevenlabs": "ElevenLabs - Paid, high quality",
|
|
581
|
-
"pyttsx3": "System TTS - Offline, uses espeak/SAPI5",
|
|
582
|
-
}
|
|
583
|
-
info.append(
|
|
584
|
-
{
|
|
585
|
-
"name": b,
|
|
586
|
-
"available": available,
|
|
587
|
-
"description": desc.get(b, ""),
|
|
588
|
-
}
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
return {
|
|
592
|
-
"success": True,
|
|
593
|
-
"backends": info,
|
|
594
|
-
"available": backends,
|
|
595
|
-
"default": backends[0] if backends else None,
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
except Exception as e:
|
|
599
|
-
return {"success": False, "error": str(e)}
|
|
600
|
-
|
|
601
|
-
async def list_voices(self, backend: str = "gtts"):
|
|
602
|
-
"""List available voices for a backend."""
|
|
603
|
-
try:
|
|
604
|
-
from . import get_tts
|
|
605
|
-
|
|
606
|
-
loop = asyncio.get_event_loop()
|
|
607
|
-
|
|
608
|
-
def do_list():
|
|
609
|
-
tts = get_tts(backend)
|
|
610
|
-
return tts.get_voices()
|
|
611
|
-
|
|
612
|
-
voices = await loop.run_in_executor(None, do_list)
|
|
329
|
+
async def announce_context(self, include_full_path: bool = False):
|
|
330
|
+
"""Announce the current working directory and git branch."""
|
|
331
|
+
import subprocess
|
|
613
332
|
|
|
614
|
-
return {
|
|
615
|
-
"success": True,
|
|
616
|
-
"backend": backend,
|
|
617
|
-
"voices": voices,
|
|
618
|
-
"count": len(voices),
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
except Exception as e:
|
|
622
|
-
return {"success": False, "error": str(e)}
|
|
623
|
-
|
|
624
|
-
async def play_audio(self, path: str):
|
|
625
|
-
"""Play an audio file."""
|
|
626
333
|
try:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if not path_obj.exists():
|
|
631
|
-
return {"success": False, "error": f"File not found: {path}"}
|
|
334
|
+
# Get current working directory
|
|
335
|
+
cwd = Path.cwd()
|
|
336
|
+
dir_name = str(cwd) if include_full_path else cwd.name
|
|
632
337
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
"success": True,
|
|
643
|
-
"played": str(path_obj),
|
|
644
|
-
"timestamp": datetime.now().isoformat(),
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
except Exception as e:
|
|
648
|
-
return {"success": False, "error": str(e)}
|
|
649
|
-
|
|
650
|
-
async def list_audio_files(self, limit: int = 20):
|
|
651
|
-
"""List generated audio files."""
|
|
652
|
-
try:
|
|
653
|
-
audio_dir = get_audio_dir()
|
|
654
|
-
if not audio_dir.exists():
|
|
655
|
-
return {"success": True, "files": [], "count": 0}
|
|
656
|
-
|
|
657
|
-
audio_files = sorted(
|
|
658
|
-
list(audio_dir.glob("*.mp3")) + list(audio_dir.glob("*.wav")),
|
|
659
|
-
key=lambda p: p.stat().st_mtime,
|
|
660
|
-
reverse=True,
|
|
661
|
-
)[:limit]
|
|
662
|
-
|
|
663
|
-
files = []
|
|
664
|
-
for f in audio_files:
|
|
665
|
-
files.append(
|
|
666
|
-
{
|
|
667
|
-
"name": f.name,
|
|
668
|
-
"path": str(f),
|
|
669
|
-
"size_kb": round(f.stat().st_size / 1024, 2),
|
|
670
|
-
"created": datetime.fromtimestamp(
|
|
671
|
-
f.stat().st_mtime
|
|
672
|
-
).isoformat(),
|
|
673
|
-
}
|
|
338
|
+
# Try to get git branch
|
|
339
|
+
git_branch = None
|
|
340
|
+
try:
|
|
341
|
+
result = subprocess.run(
|
|
342
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
343
|
+
capture_output=True,
|
|
344
|
+
text=True,
|
|
345
|
+
timeout=5,
|
|
346
|
+
cwd=str(cwd),
|
|
674
347
|
)
|
|
348
|
+
if result.returncode == 0:
|
|
349
|
+
git_branch = result.stdout.strip()
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
675
352
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
"
|
|
681
|
-
"count": len(files),
|
|
682
|
-
"total_size_mb": round(total_size / (1024 * 1024), 2),
|
|
683
|
-
"audio_dir": str(audio_dir),
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
except Exception as e:
|
|
687
|
-
return {"success": False, "error": str(e)}
|
|
688
|
-
|
|
689
|
-
async def clear_audio_cache(self, max_age_hours: float = 24):
|
|
690
|
-
"""Clear audio cache."""
|
|
691
|
-
try:
|
|
692
|
-
audio_dir = get_audio_dir()
|
|
693
|
-
if not audio_dir.exists():
|
|
694
|
-
return {"success": True, "deleted": 0}
|
|
695
|
-
|
|
696
|
-
deleted = 0
|
|
697
|
-
now = datetime.now()
|
|
353
|
+
# Build announcement text
|
|
354
|
+
if git_branch:
|
|
355
|
+
text = f"Working in {dir_name}, on branch {git_branch}"
|
|
356
|
+
else:
|
|
357
|
+
text = f"Working in {dir_name}"
|
|
698
358
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
f.unlink()
|
|
703
|
-
deleted += 1
|
|
704
|
-
else:
|
|
705
|
-
mtime = datetime.fromtimestamp(f.stat().st_mtime)
|
|
706
|
-
age_hours = (now - mtime).total_seconds() / 3600
|
|
707
|
-
if age_hours > max_age_hours:
|
|
708
|
-
f.unlink()
|
|
709
|
-
deleted += 1
|
|
710
|
-
except Exception:
|
|
711
|
-
pass
|
|
359
|
+
# Speak the announcement
|
|
360
|
+
await self.start_queue_processor()
|
|
361
|
+
result = await self.speak(text=text)
|
|
712
362
|
|
|
713
363
|
return {
|
|
714
364
|
"success": True,
|
|
715
|
-
"
|
|
716
|
-
"
|
|
365
|
+
"directory": str(cwd),
|
|
366
|
+
"directory_name": cwd.name,
|
|
367
|
+
"git_branch": git_branch,
|
|
368
|
+
"announced_text": text,
|
|
369
|
+
"speak_result": result,
|
|
717
370
|
}
|
|
718
371
|
|
|
719
372
|
except Exception as e:
|
|
720
373
|
return {"success": False, "error": str(e)}
|
|
721
374
|
|
|
722
|
-
async def check_audio_status(self):
|
|
723
|
-
"""Check WSL audio connectivity and available playback methods."""
|
|
724
|
-
try:
|
|
725
|
-
from . import check_wsl_audio
|
|
726
|
-
|
|
727
|
-
status = check_wsl_audio()
|
|
728
|
-
status["success"] = True
|
|
729
|
-
status["timestamp"] = datetime.now().isoformat()
|
|
730
|
-
return status
|
|
731
|
-
|
|
732
|
-
except Exception as e:
|
|
733
|
-
return {"success": False, "error": str(e)}
|
|
734
|
-
|
|
735
375
|
|
|
736
376
|
async def main():
|
|
737
377
|
"""Main entry point for the MCP server."""
|
|
@@ -742,7 +382,7 @@ async def main():
|
|
|
742
382
|
write_stream,
|
|
743
383
|
InitializationOptions(
|
|
744
384
|
server_name="scitex-audio",
|
|
745
|
-
server_version="0.
|
|
385
|
+
server_version="0.3.0", # Bumped for cross-process FIFO
|
|
746
386
|
capabilities=server.server.get_capabilities(
|
|
747
387
|
notification_options=NotificationOptions(),
|
|
748
388
|
experimental_capabilities={},
|
|
@@ -754,4 +394,5 @@ async def main():
|
|
|
754
394
|
if __name__ == "__main__":
|
|
755
395
|
asyncio.run(main())
|
|
756
396
|
|
|
397
|
+
|
|
757
398
|
# EOF
|