scitex 2.14.0__py3-none-any.whl → 2.15.2__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 +71 -17
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +210 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/template.py +24 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +129 -61
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +7 -2
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +481 -0
- scitex/cli/main.py +200 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/plt.py +357 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +23 -8
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/stats.py +15 -8
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +132 -8
- scitex/cloud/__init__.py +41 -2
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +256 -0
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +82 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +41 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
- scitex/introspect/_mcp/__init__.py +41 -0
- scitex/introspect/_mcp/handlers.py +233 -0
- scitex/introspect/_members.py +155 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/_save.py +1 -2
- scitex/io/bundle/README.md +1 -1
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +98 -5
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +245 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/README.md +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +155 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/README.md +1 -1
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/template/clone_writer_directory.py +5 -5
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.2.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-24
|
|
3
|
+
# File: src/scitex/cli/scholar/_crossref_scitex.py
|
|
4
|
+
"""CrossRef-SciTeX CLI commands for scitex scholar.
|
|
5
|
+
|
|
6
|
+
Provides access to the local CrossRef database (167M+ papers) via crossref-local.
|
|
7
|
+
Branded as crossref-scitex to distinguish from official CrossRef API.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group("crossref-scitex")
|
|
19
|
+
def crossref_scitex():
|
|
20
|
+
"""
|
|
21
|
+
CrossRef-SciTeX database search (167M+ papers)
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Search and query the local CrossRef database via crossref-local.
|
|
25
|
+
Supports both direct DB access and HTTP API mode.
|
|
26
|
+
|
|
27
|
+
\b
|
|
28
|
+
Examples:
|
|
29
|
+
scitex scholar crossref-scitex search "deep learning"
|
|
30
|
+
scitex scholar crossref-scitex get 10.1038/nature12373
|
|
31
|
+
scitex scholar crossref-scitex count "epilepsy seizure"
|
|
32
|
+
scitex scholar crossref-scitex info
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@crossref_scitex.command("search")
|
|
38
|
+
@click.argument("query")
|
|
39
|
+
@click.option("-n", "--limit", default=20, help="Maximum results (default: 20)")
|
|
40
|
+
@click.option("--offset", default=0, help="Skip N results for pagination")
|
|
41
|
+
@click.option("--year-min", type=int, help="Minimum publication year")
|
|
42
|
+
@click.option("--year-max", type=int, help="Maximum publication year")
|
|
43
|
+
@click.option("--enrich", is_flag=True, help="Add citation counts and references")
|
|
44
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
45
|
+
def search_cmd(query, limit, offset, year_min, year_max, enrich, as_json):
|
|
46
|
+
"""
|
|
47
|
+
Search for papers in CrossRef database.
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
Examples:
|
|
51
|
+
scitex scholar crossref-scitex search "hippocampal sharp wave ripples"
|
|
52
|
+
scitex scholar crossref search "deep learning" --limit 50
|
|
53
|
+
scitex scholar crossref search "CRISPR" --year-min 2020 --enrich
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
from scitex.scholar import crossref_scitex as crossref
|
|
57
|
+
except ImportError:
|
|
58
|
+
click.secho(
|
|
59
|
+
"crossref-local not installed. Install with: pip install crossref-local",
|
|
60
|
+
fg="red",
|
|
61
|
+
)
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
results = crossref.search(query, limit=limit, offset=offset)
|
|
66
|
+
|
|
67
|
+
if enrich:
|
|
68
|
+
results = crossref.enrich(results)
|
|
69
|
+
|
|
70
|
+
papers = []
|
|
71
|
+
for work in results:
|
|
72
|
+
if year_min and work.year and work.year < year_min:
|
|
73
|
+
continue
|
|
74
|
+
if year_max and work.year and work.year > year_max:
|
|
75
|
+
continue
|
|
76
|
+
papers.append(work)
|
|
77
|
+
if len(papers) >= limit:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
if as_json:
|
|
81
|
+
output = {
|
|
82
|
+
"query": query,
|
|
83
|
+
"total": results.total,
|
|
84
|
+
"count": len(papers),
|
|
85
|
+
"papers": [
|
|
86
|
+
{
|
|
87
|
+
"doi": p.doi,
|
|
88
|
+
"title": p.title,
|
|
89
|
+
"authors": p.authors,
|
|
90
|
+
"year": p.year,
|
|
91
|
+
"journal": p.journal,
|
|
92
|
+
"citation_count": p.citation_count,
|
|
93
|
+
}
|
|
94
|
+
for p in papers
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
click.echo(json.dumps(output, indent=2))
|
|
98
|
+
else:
|
|
99
|
+
click.secho(
|
|
100
|
+
f"Found {results.total} papers for: {query}", fg="green", bold=True
|
|
101
|
+
)
|
|
102
|
+
click.echo()
|
|
103
|
+
|
|
104
|
+
for i, paper in enumerate(papers, 1):
|
|
105
|
+
authors = ", ".join(paper.authors[:3]) if paper.authors else "Unknown"
|
|
106
|
+
if paper.authors and len(paper.authors) > 3:
|
|
107
|
+
authors += " et al."
|
|
108
|
+
|
|
109
|
+
click.secho(f"{i}. {paper.title}", fg="cyan", bold=True)
|
|
110
|
+
click.echo(f" Authors: {authors}")
|
|
111
|
+
click.echo(
|
|
112
|
+
f" Year: {paper.year or 'N/A'} | Journal: {paper.journal or 'N/A'}"
|
|
113
|
+
)
|
|
114
|
+
click.echo(f" DOI: {paper.doi}")
|
|
115
|
+
if paper.citation_count:
|
|
116
|
+
click.echo(f" Citations: {paper.citation_count}")
|
|
117
|
+
click.echo()
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
click.secho(f"Error: {e}", fg="red")
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@crossref_scitex.command("get")
|
|
125
|
+
@click.argument("doi")
|
|
126
|
+
@click.option("--citations", is_flag=True, help="Include citing papers")
|
|
127
|
+
@click.option("--references", is_flag=True, help="Include referenced papers")
|
|
128
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
129
|
+
def get_cmd(doi, citations, references, as_json):
|
|
130
|
+
"""
|
|
131
|
+
Get paper details by DOI.
|
|
132
|
+
|
|
133
|
+
\b
|
|
134
|
+
Examples:
|
|
135
|
+
scitex scholar crossref get 10.1038/nature12373
|
|
136
|
+
scitex scholar crossref get 10.1126/science.aax0758 --citations
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
from scitex.scholar import crossref_scitex as crossref
|
|
140
|
+
except ImportError:
|
|
141
|
+
click.secho(
|
|
142
|
+
"crossref-local not installed. Install with: pip install crossref-local",
|
|
143
|
+
fg="red",
|
|
144
|
+
)
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
work = crossref.get(doi)
|
|
149
|
+
|
|
150
|
+
if work is None:
|
|
151
|
+
click.secho(f"DOI not found: {doi}", fg="red")
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
if as_json:
|
|
155
|
+
output = {
|
|
156
|
+
"doi": work.doi,
|
|
157
|
+
"title": work.title,
|
|
158
|
+
"authors": work.authors,
|
|
159
|
+
"year": work.year,
|
|
160
|
+
"journal": work.journal,
|
|
161
|
+
"abstract": work.abstract,
|
|
162
|
+
"citation_count": work.citation_count,
|
|
163
|
+
"reference_count": work.reference_count,
|
|
164
|
+
"type": work.type,
|
|
165
|
+
"publisher": work.publisher,
|
|
166
|
+
}
|
|
167
|
+
if citations:
|
|
168
|
+
output["citing_dois"] = crossref.get_citing(doi)
|
|
169
|
+
if references:
|
|
170
|
+
output["referenced_dois"] = crossref.get_cited(doi)
|
|
171
|
+
click.echo(json.dumps(output, indent=2))
|
|
172
|
+
else:
|
|
173
|
+
click.secho(work.title, fg="cyan", bold=True)
|
|
174
|
+
click.echo()
|
|
175
|
+
|
|
176
|
+
if work.authors:
|
|
177
|
+
click.echo(f"Authors: {', '.join(work.authors)}")
|
|
178
|
+
click.echo(f"Year: {work.year or 'N/A'}")
|
|
179
|
+
click.echo(f"Journal: {work.journal or 'N/A'}")
|
|
180
|
+
click.echo(f"DOI: {work.doi}")
|
|
181
|
+
click.echo(f"Type: {work.type or 'N/A'}")
|
|
182
|
+
click.echo(f"Publisher: {work.publisher or 'N/A'}")
|
|
183
|
+
click.echo(f"Citations: {work.citation_count or 0}")
|
|
184
|
+
click.echo(f"References: {work.reference_count or 0}")
|
|
185
|
+
|
|
186
|
+
if work.abstract:
|
|
187
|
+
click.echo()
|
|
188
|
+
click.secho("Abstract:", bold=True)
|
|
189
|
+
click.echo(
|
|
190
|
+
work.abstract[:500] + "..."
|
|
191
|
+
if len(work.abstract) > 500
|
|
192
|
+
else work.abstract
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if citations:
|
|
196
|
+
citing = crossref.get_citing(doi)
|
|
197
|
+
click.echo()
|
|
198
|
+
click.secho(f"Citing papers ({len(citing)}):", bold=True)
|
|
199
|
+
for c_doi in citing[:10]:
|
|
200
|
+
click.echo(f" - {c_doi}")
|
|
201
|
+
if len(citing) > 10:
|
|
202
|
+
click.echo(f" ... and {len(citing) - 10} more")
|
|
203
|
+
|
|
204
|
+
if references:
|
|
205
|
+
cited = crossref.get_cited(doi)
|
|
206
|
+
click.echo()
|
|
207
|
+
click.secho(f"References ({len(cited)}):", bold=True)
|
|
208
|
+
for r_doi in cited[:10]:
|
|
209
|
+
click.echo(f" - {r_doi}")
|
|
210
|
+
if len(cited) > 10:
|
|
211
|
+
click.echo(f" ... and {len(cited) - 10} more")
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
click.secho(f"Error: {e}", fg="red")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@crossref_scitex.command("count")
|
|
219
|
+
@click.argument("query")
|
|
220
|
+
def count_cmd(query):
|
|
221
|
+
"""
|
|
222
|
+
Count papers matching a query.
|
|
223
|
+
|
|
224
|
+
\b
|
|
225
|
+
Examples:
|
|
226
|
+
scitex scholar crossref count "machine learning"
|
|
227
|
+
scitex scholar crossref count "CRISPR gene editing"
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
from scitex.scholar import crossref_scitex as crossref
|
|
231
|
+
except ImportError:
|
|
232
|
+
click.secho(
|
|
233
|
+
"crossref-local not installed. Install with: pip install crossref-local",
|
|
234
|
+
fg="red",
|
|
235
|
+
)
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
count = crossref.count(query)
|
|
240
|
+
click.echo(f"{count:,} papers match: {query}")
|
|
241
|
+
except Exception as e:
|
|
242
|
+
click.secho(f"Error: {e}", fg="red")
|
|
243
|
+
sys.exit(1)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@crossref_scitex.command("info")
|
|
247
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
248
|
+
def info_cmd(as_json):
|
|
249
|
+
"""
|
|
250
|
+
Show CrossRef database configuration and status.
|
|
251
|
+
|
|
252
|
+
\b
|
|
253
|
+
Examples:
|
|
254
|
+
scitex scholar crossref info
|
|
255
|
+
scitex scholar crossref info --json
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
from scitex.scholar import crossref_scitex as crossref
|
|
259
|
+
except ImportError:
|
|
260
|
+
click.secho(
|
|
261
|
+
"crossref-local not installed. Install with: pip install crossref-local",
|
|
262
|
+
fg="red",
|
|
263
|
+
)
|
|
264
|
+
sys.exit(1)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
info = crossref.info()
|
|
268
|
+
mode = crossref.get_mode()
|
|
269
|
+
|
|
270
|
+
if as_json:
|
|
271
|
+
info["mode"] = mode
|
|
272
|
+
click.echo(json.dumps(info, indent=2))
|
|
273
|
+
else:
|
|
274
|
+
click.secho("CrossRef Database Status", fg="cyan", bold=True)
|
|
275
|
+
click.echo()
|
|
276
|
+
click.echo(f"Mode: {mode}")
|
|
277
|
+
click.echo(f"Status: {info.get('status', 'unknown')}")
|
|
278
|
+
|
|
279
|
+
if "version" in info:
|
|
280
|
+
click.echo(f"Version: {info['version']}")
|
|
281
|
+
|
|
282
|
+
if "work_count" in info:
|
|
283
|
+
click.echo(f"Papers: {info['work_count']:,}")
|
|
284
|
+
|
|
285
|
+
if "db_path" in info:
|
|
286
|
+
click.echo(f"Database: {info['db_path']}")
|
|
287
|
+
|
|
288
|
+
if "api_url" in info:
|
|
289
|
+
click.echo(f"API URL: {info['api_url']}")
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
click.secho(f"Error: {e}", fg="red")
|
|
293
|
+
sys.exit(1)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# EOF
|
scitex/cli/scholar/_fetch.py
CHANGED
|
@@ -219,13 +219,35 @@ def _add_single(
|
|
|
219
219
|
project=project,
|
|
220
220
|
force=force,
|
|
221
221
|
)
|
|
222
|
+
|
|
223
|
+
# Granular success flags
|
|
224
|
+
has_doi = bool(paper and paper.metadata.id.doi)
|
|
225
|
+
has_metadata = bool(paper and paper.metadata.basic.title)
|
|
226
|
+
has_pdf = bool(symlink_path)
|
|
227
|
+
has_content = bool(
|
|
228
|
+
paper
|
|
229
|
+
and hasattr(paper, "container")
|
|
230
|
+
and paper.container.pdf_size_bytes
|
|
231
|
+
and paper.container.pdf_size_bytes > 0
|
|
232
|
+
)
|
|
233
|
+
pdf_method = None
|
|
234
|
+
if paper and paper.metadata.path.pdfs_engines:
|
|
235
|
+
pdf_method = paper.metadata.path.pdfs_engines[0]
|
|
236
|
+
|
|
222
237
|
return {
|
|
223
|
-
"success":
|
|
224
|
-
"
|
|
238
|
+
"success": has_pdf, # Overall success = PDF obtained
|
|
239
|
+
"success_doi": has_doi,
|
|
240
|
+
"success_metadata": has_metadata,
|
|
241
|
+
"success_pdf": has_pdf,
|
|
242
|
+
"success_content": has_content,
|
|
243
|
+
"pdf_method": pdf_method,
|
|
244
|
+
"message": "Paper fetched"
|
|
245
|
+
if has_pdf
|
|
246
|
+
else "Metadata fetched but PDF not downloaded",
|
|
225
247
|
"doi": paper.metadata.id.doi if paper else None,
|
|
226
248
|
"title": paper.metadata.basic.title if paper else None,
|
|
227
249
|
"path": str(symlink_path) if symlink_path else None,
|
|
228
|
-
"has_pdf":
|
|
250
|
+
"has_pdf": has_pdf,
|
|
229
251
|
"timestamp": datetime.now().isoformat(),
|
|
230
252
|
}
|
|
231
253
|
|
scitex/cli/social.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-22
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/cli/social.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
SciTeX CLI - Social Media Commands
|
|
7
|
+
|
|
8
|
+
Thin wrapper around socialia CLI with SCITEX_ environment variable prefix.
|
|
9
|
+
All commands delegate to socialia for reproducibility.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _run_socialia(*args, json_output: bool = False) -> int:
|
|
19
|
+
"""Run socialia CLI command."""
|
|
20
|
+
cmd = [sys.executable, "-m", "socialia"]
|
|
21
|
+
if json_output:
|
|
22
|
+
cmd.append("--json")
|
|
23
|
+
cmd.extend(args)
|
|
24
|
+
|
|
25
|
+
result = subprocess.run(cmd)
|
|
26
|
+
return result.returncode
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _check_socialia() -> bool:
|
|
30
|
+
"""Check if socialia is available."""
|
|
31
|
+
try:
|
|
32
|
+
import socialia # noqa: F401
|
|
33
|
+
|
|
34
|
+
return True
|
|
35
|
+
except ImportError:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.group(
|
|
40
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
41
|
+
invoke_without_command=True,
|
|
42
|
+
)
|
|
43
|
+
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
44
|
+
@click.pass_context
|
|
45
|
+
def social(ctx, help_recursive):
|
|
46
|
+
"""
|
|
47
|
+
Social media management (powered by socialia)
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
Platforms:
|
|
51
|
+
twitter - Twitter/X posting and management
|
|
52
|
+
linkedin - LinkedIn posting
|
|
53
|
+
reddit - Reddit posting
|
|
54
|
+
youtube - YouTube video uploads
|
|
55
|
+
|
|
56
|
+
\b
|
|
57
|
+
Examples:
|
|
58
|
+
scitex social post twitter "Hello from SciTeX!"
|
|
59
|
+
scitex social post linkedin "Research update"
|
|
60
|
+
scitex social status
|
|
61
|
+
scitex social analytics twitter
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
Environment Variables (SCITEX_SOCIAL_ prefix):
|
|
65
|
+
SCITEX_SOCIAL_X_CONSUMER_KEY Twitter API keys
|
|
66
|
+
SCITEX_SOCIAL_LINKEDIN_ACCESS_TOKEN LinkedIn OAuth token
|
|
67
|
+
SCITEX_SOCIAL_REDDIT_CLIENT_ID Reddit app credentials
|
|
68
|
+
SCITEX_SOCIAL_YOUTUBE_API_KEY YouTube API key
|
|
69
|
+
|
|
70
|
+
Note: Falls back to SOCIALIA_ prefix if SCITEX_SOCIAL_ not set.
|
|
71
|
+
"""
|
|
72
|
+
if not _check_socialia():
|
|
73
|
+
click.secho("Error: socialia not installed", fg="red", err=True)
|
|
74
|
+
click.echo("\nInstall with: pip install socialia")
|
|
75
|
+
ctx.exit(1)
|
|
76
|
+
|
|
77
|
+
if help_recursive:
|
|
78
|
+
from . import print_help_recursive
|
|
79
|
+
|
|
80
|
+
print_help_recursive(ctx, social)
|
|
81
|
+
ctx.exit(0)
|
|
82
|
+
elif ctx.invoked_subcommand is None:
|
|
83
|
+
click.echo(ctx.get_help())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@social.command()
|
|
87
|
+
@click.argument(
|
|
88
|
+
"platform", type=click.Choice(["twitter", "linkedin", "reddit", "youtube"])
|
|
89
|
+
)
|
|
90
|
+
@click.argument("text", required=False)
|
|
91
|
+
@click.option(
|
|
92
|
+
"-f", "--file", type=click.Path(exists=True), help="Read content from file"
|
|
93
|
+
)
|
|
94
|
+
@click.option("--reply-to", help="Post ID to reply to (Twitter)")
|
|
95
|
+
@click.option("--quote", help="Post ID to quote (Twitter)")
|
|
96
|
+
@click.option("-s", "--subreddit", default="test", help="Target subreddit (Reddit)")
|
|
97
|
+
@click.option("-t", "--title", help="Post title (Reddit/YouTube)")
|
|
98
|
+
@click.option("--dry-run", is_flag=True, help="Preview without posting")
|
|
99
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
100
|
+
def post(platform, text, file, reply_to, quote, subreddit, title, dry_run, as_json):
|
|
101
|
+
"""
|
|
102
|
+
Post content to a social platform
|
|
103
|
+
|
|
104
|
+
\b
|
|
105
|
+
Examples:
|
|
106
|
+
scitex social post twitter "Hello world!"
|
|
107
|
+
scitex social post twitter --file tweet.txt
|
|
108
|
+
scitex social post reddit "Check this out" -s python -t "Cool project"
|
|
109
|
+
scitex social post linkedin "Professional update"
|
|
110
|
+
scitex social post twitter "Test" --dry-run
|
|
111
|
+
"""
|
|
112
|
+
args = ["post", platform]
|
|
113
|
+
|
|
114
|
+
if text:
|
|
115
|
+
args.append(text)
|
|
116
|
+
if file:
|
|
117
|
+
args.extend(["--file", file])
|
|
118
|
+
if reply_to:
|
|
119
|
+
args.extend(["--reply-to", reply_to])
|
|
120
|
+
if quote:
|
|
121
|
+
args.extend(["--quote", quote])
|
|
122
|
+
if subreddit and platform == "reddit":
|
|
123
|
+
args.extend(["--subreddit", subreddit])
|
|
124
|
+
if title:
|
|
125
|
+
args.extend(["--title", title])
|
|
126
|
+
if dry_run:
|
|
127
|
+
args.append("--dry-run")
|
|
128
|
+
|
|
129
|
+
sys.exit(_run_socialia(*args, json_output=as_json))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@social.command()
|
|
133
|
+
@click.argument("platform", type=click.Choice(["twitter", "linkedin", "reddit"]))
|
|
134
|
+
@click.argument("post_id")
|
|
135
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
136
|
+
def delete(platform, post_id, as_json):
|
|
137
|
+
"""
|
|
138
|
+
Delete a post from a platform
|
|
139
|
+
|
|
140
|
+
\b
|
|
141
|
+
Examples:
|
|
142
|
+
scitex social delete twitter 1234567890
|
|
143
|
+
scitex social delete reddit abc123
|
|
144
|
+
"""
|
|
145
|
+
sys.exit(_run_socialia("delete", platform, post_id, json_output=as_json))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@social.command()
|
|
149
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
150
|
+
def status(as_json):
|
|
151
|
+
"""
|
|
152
|
+
Check configuration and authentication status
|
|
153
|
+
|
|
154
|
+
\b
|
|
155
|
+
Example:
|
|
156
|
+
scitex social status
|
|
157
|
+
scitex social status --json
|
|
158
|
+
"""
|
|
159
|
+
sys.exit(_run_socialia("status", json_output=as_json))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@social.command()
|
|
163
|
+
@click.argument(
|
|
164
|
+
"platform",
|
|
165
|
+
type=click.Choice(["twitter", "linkedin", "reddit", "youtube"]),
|
|
166
|
+
required=False,
|
|
167
|
+
)
|
|
168
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
169
|
+
def check(platform, as_json):
|
|
170
|
+
"""
|
|
171
|
+
Check platform connection status.
|
|
172
|
+
|
|
173
|
+
\b
|
|
174
|
+
Examples:
|
|
175
|
+
scitex social check # Check all platforms
|
|
176
|
+
scitex social check twitter # Check specific platform
|
|
177
|
+
"""
|
|
178
|
+
args = ["check"]
|
|
179
|
+
if platform:
|
|
180
|
+
args.append(platform)
|
|
181
|
+
sys.exit(_run_socialia(*args, json_output=as_json))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@social.command()
|
|
185
|
+
@click.argument(
|
|
186
|
+
"platform",
|
|
187
|
+
type=click.Choice(["twitter", "linkedin", "reddit"]),
|
|
188
|
+
required=False,
|
|
189
|
+
)
|
|
190
|
+
@click.option("--limit", "-l", type=int, default=10, help="Number of posts to fetch")
|
|
191
|
+
@click.option("--mentions", is_flag=True, help="Get mentions/notifications instead")
|
|
192
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
193
|
+
def feed(platform, limit, mentions, as_json):
|
|
194
|
+
"""
|
|
195
|
+
Get recent posts from platform feeds.
|
|
196
|
+
|
|
197
|
+
\b
|
|
198
|
+
Examples:
|
|
199
|
+
scitex social feed # All platforms
|
|
200
|
+
scitex social feed twitter --limit 5 # Specific platform
|
|
201
|
+
scitex social feed --mentions # Get mentions
|
|
202
|
+
"""
|
|
203
|
+
args = ["feed"]
|
|
204
|
+
if platform:
|
|
205
|
+
args.append(platform)
|
|
206
|
+
args.extend(["--limit", str(limit)])
|
|
207
|
+
if mentions:
|
|
208
|
+
args.append("--mentions")
|
|
209
|
+
sys.exit(_run_socialia(*args, json_output=as_json))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@social.command()
|
|
213
|
+
@click.argument("platform", type=click.Choice(["twitter", "linkedin", "reddit"]))
|
|
214
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
215
|
+
def me(platform, as_json):
|
|
216
|
+
"""
|
|
217
|
+
Get user profile information.
|
|
218
|
+
|
|
219
|
+
\b
|
|
220
|
+
Examples:
|
|
221
|
+
scitex social me twitter
|
|
222
|
+
scitex social me linkedin --json
|
|
223
|
+
"""
|
|
224
|
+
sys.exit(_run_socialia("me", platform, json_output=as_json))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@social.command()
|
|
228
|
+
@click.argument("platform", type=click.Choice(["twitter", "youtube", "ga"]))
|
|
229
|
+
@click.option("--days", "-d", type=int, default=7, help="Number of days to analyze")
|
|
230
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
231
|
+
def analytics(platform, days, as_json):
|
|
232
|
+
"""
|
|
233
|
+
Get analytics for a platform
|
|
234
|
+
|
|
235
|
+
\b
|
|
236
|
+
Platforms:
|
|
237
|
+
twitter - Tweet engagement metrics
|
|
238
|
+
youtube - Channel and video statistics
|
|
239
|
+
ga - Google Analytics reports
|
|
240
|
+
|
|
241
|
+
\b
|
|
242
|
+
Examples:
|
|
243
|
+
scitex social analytics twitter
|
|
244
|
+
scitex social analytics youtube --days 30
|
|
245
|
+
scitex social analytics ga --json
|
|
246
|
+
"""
|
|
247
|
+
args = ["analytics", platform, "--days", str(days)]
|
|
248
|
+
sys.exit(_run_socialia(*args, json_output=as_json))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@social.command()
|
|
252
|
+
@click.argument("platform", type=click.Choice(["twitter"]))
|
|
253
|
+
@click.option(
|
|
254
|
+
"-f", "--file", type=click.Path(exists=True), required=True, help="Thread file"
|
|
255
|
+
)
|
|
256
|
+
@click.option("--delay", type=int, default=2, help="Delay between posts (seconds)")
|
|
257
|
+
@click.option("--dry-run", is_flag=True, help="Preview without posting")
|
|
258
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
259
|
+
def thread(platform, file, delay, dry_run, as_json):
|
|
260
|
+
"""
|
|
261
|
+
Post a thread (multiple connected posts)
|
|
262
|
+
|
|
263
|
+
\b
|
|
264
|
+
Thread file format (--- separates posts):
|
|
265
|
+
First tweet in the thread
|
|
266
|
+
---
|
|
267
|
+
Second tweet, replying to first
|
|
268
|
+
---
|
|
269
|
+
Third tweet, and so on...
|
|
270
|
+
|
|
271
|
+
\b
|
|
272
|
+
Example:
|
|
273
|
+
scitex social thread twitter --file thread.txt
|
|
274
|
+
scitex social thread twitter --file thread.txt --dry-run
|
|
275
|
+
"""
|
|
276
|
+
args = ["thread", platform, "--file", file, "--delay", str(delay)]
|
|
277
|
+
if dry_run:
|
|
278
|
+
args.append("--dry-run")
|
|
279
|
+
sys.exit(_run_socialia(*args, json_output=as_json))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@social.command()
|
|
283
|
+
@click.option(
|
|
284
|
+
"-t",
|
|
285
|
+
"--transport",
|
|
286
|
+
type=click.Choice(["stdio", "sse", "http"]),
|
|
287
|
+
default="stdio",
|
|
288
|
+
help="Transport protocol",
|
|
289
|
+
)
|
|
290
|
+
@click.option("--host", default="0.0.0.0", help="Host for HTTP/SSE transport")
|
|
291
|
+
@click.option("--port", default=8086, type=int, help="Port for HTTP/SSE transport")
|
|
292
|
+
def serve(transport, host, port):
|
|
293
|
+
"""
|
|
294
|
+
Run socialia MCP server
|
|
295
|
+
|
|
296
|
+
\b
|
|
297
|
+
Examples:
|
|
298
|
+
scitex social serve # stdio for Claude Desktop
|
|
299
|
+
scitex social serve -t http --port 8086
|
|
300
|
+
"""
|
|
301
|
+
args = ["mcp", "run", "--transport", transport]
|
|
302
|
+
if transport != "stdio":
|
|
303
|
+
args.extend(["--host", host, "--port", str(port)])
|
|
304
|
+
click.secho(f"Starting socialia MCP server ({transport})", fg="cyan")
|
|
305
|
+
click.echo(f" Host: {host}")
|
|
306
|
+
click.echo(f" Port: {port}")
|
|
307
|
+
|
|
308
|
+
sys.exit(_run_socialia(*args))
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
social()
|
|
313
|
+
|
|
314
|
+
# EOF
|
scitex/cli/stats.py
CHANGED
|
@@ -11,8 +11,13 @@ from pathlib import Path
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@click.group(
|
|
15
|
-
|
|
14
|
+
@click.group(
|
|
15
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
16
|
+
invoke_without_command=True,
|
|
17
|
+
)
|
|
18
|
+
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
def stats(ctx, help_recursive):
|
|
16
21
|
"""
|
|
17
22
|
Statistical analysis and testing utilities
|
|
18
23
|
|
|
@@ -29,20 +34,22 @@ def stats():
|
|
|
29
34
|
scitex stats describe data.csv
|
|
30
35
|
scitex stats save results.json --output analysis.stats
|
|
31
36
|
"""
|
|
32
|
-
|
|
37
|
+
if help_recursive:
|
|
38
|
+
_print_help_recursive(ctx)
|
|
39
|
+
ctx.exit(0)
|
|
40
|
+
elif ctx.invoked_subcommand is None:
|
|
41
|
+
click.echo(ctx.get_help())
|
|
33
42
|
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def help_recursive(ctx):
|
|
38
|
-
"""Show help for all commands recursively."""
|
|
44
|
+
def _print_help_recursive(ctx):
|
|
45
|
+
"""Print help for all commands recursively."""
|
|
39
46
|
fake_parent = click.Context(click.Group(), info_name="scitex")
|
|
40
47
|
parent_ctx = click.Context(stats, info_name="stats", parent=fake_parent)
|
|
41
48
|
click.secho("━━━ scitex stats ━━━", fg="cyan", bold=True)
|
|
42
49
|
click.echo(stats.get_help(parent_ctx))
|
|
43
50
|
for name in sorted(stats.list_commands(ctx) or []):
|
|
44
51
|
cmd = stats.get_command(ctx, name)
|
|
45
|
-
if cmd is None
|
|
52
|
+
if cmd is None:
|
|
46
53
|
continue
|
|
47
54
|
click.echo()
|
|
48
55
|
click.secho(f"━━━ scitex stats {name} ━━━", fg="cyan", bold=True)
|