scitex 2.14.0__py3-none-any.whl → 2.15.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. scitex/__init__.py +47 -0
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +191 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/writer.py +21 -204
  17. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  18. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  19. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  20. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  21. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  22. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  23. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  24. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  25. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  26. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  27. scitex/audio/README.md +40 -36
  28. scitex/audio/__init__.py +127 -59
  29. scitex/audio/_branding.py +185 -0
  30. scitex/audio/_mcp/__init__.py +32 -0
  31. scitex/audio/_mcp/handlers.py +59 -6
  32. scitex/audio/_mcp/speak_handlers.py +238 -0
  33. scitex/audio/_relay.py +225 -0
  34. scitex/audio/engines/elevenlabs_engine.py +6 -1
  35. scitex/audio/mcp_server.py +228 -75
  36. scitex/canvas/README.md +1 -1
  37. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  38. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  39. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  40. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  41. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  42. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  43. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  44. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  45. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  46. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  47. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  48. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  49. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  50. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  51. scitex/cli/__init__.py +38 -43
  52. scitex/cli/audio.py +76 -27
  53. scitex/cli/capture.py +13 -20
  54. scitex/cli/introspect.py +443 -0
  55. scitex/cli/main.py +198 -109
  56. scitex/cli/mcp.py +60 -34
  57. scitex/cli/scholar/__init__.py +8 -0
  58. scitex/cli/scholar/_crossref_scitex.py +296 -0
  59. scitex/cli/scholar/_fetch.py +25 -3
  60. scitex/cli/social.py +314 -0
  61. scitex/cli/writer.py +117 -0
  62. scitex/config/README.md +1 -1
  63. scitex/config/__init__.py +16 -2
  64. scitex/config/_env_registry.py +191 -0
  65. scitex/diagram/__init__.py +42 -19
  66. scitex/diagram/mcp_server.py +13 -125
  67. scitex/introspect/__init__.py +75 -0
  68. scitex/introspect/_call_graph.py +303 -0
  69. scitex/introspect/_class_hierarchy.py +163 -0
  70. scitex/introspect/_core.py +42 -0
  71. scitex/introspect/_docstring.py +131 -0
  72. scitex/introspect/_examples.py +113 -0
  73. scitex/introspect/_imports.py +271 -0
  74. scitex/introspect/_mcp/__init__.py +37 -0
  75. scitex/introspect/_mcp/handlers.py +208 -0
  76. scitex/introspect/_members.py +151 -0
  77. scitex/introspect/_resolve.py +89 -0
  78. scitex/introspect/_signature.py +131 -0
  79. scitex/introspect/_source.py +80 -0
  80. scitex/introspect/_type_hints.py +172 -0
  81. scitex/io/bundle/README.md +1 -1
  82. scitex/mcp_server.py +98 -5
  83. scitex/plt/__init__.py +248 -550
  84. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  85. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  86. scitex/plt/gallery/README.md +1 -1
  87. scitex/plt/utils/_hitmap/__init__.py +82 -0
  88. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  89. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  90. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  91. scitex/plt/utils/_hitmap/_constants.py +40 -0
  92. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  93. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  94. scitex/plt/utils/_hitmap/_query.py +113 -0
  95. scitex/plt/utils/_hitmap.py +46 -1616
  96. scitex/plt/utils/_metadata/__init__.py +80 -0
  97. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  98. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  99. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  100. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  101. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  102. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  103. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  104. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  105. scitex/plt/utils/_metadata/_csv.py +416 -0
  106. scitex/plt/utils/_metadata/_detect.py +225 -0
  107. scitex/plt/utils/_metadata/_legend.py +127 -0
  108. scitex/plt/utils/_metadata/_rounding.py +117 -0
  109. scitex/plt/utils/_metadata/_verification.py +202 -0
  110. scitex/schema/README.md +1 -1
  111. scitex/scholar/__init__.py +8 -0
  112. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  113. scitex/scholar/core/Scholar.py +63 -1700
  114. scitex/scholar/core/_mixins/__init__.py +36 -0
  115. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  116. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  117. scitex/scholar/core/_mixins/_loaders.py +103 -0
  118. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  119. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  120. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  121. scitex/scholar/core/_mixins/_savers.py +69 -0
  122. scitex/scholar/core/_mixins/_search.py +103 -0
  123. scitex/scholar/core/_mixins/_services.py +88 -0
  124. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  125. scitex/scholar/crossref_scitex.py +367 -0
  126. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  127. scitex/scholar/examples/00_run_all.sh +120 -0
  128. scitex/scholar/jobs/_executors.py +27 -3
  129. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  130. scitex/scholar/pdf_download/_cli.py +154 -0
  131. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  132. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  133. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  134. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  135. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  136. scitex/scholar/pipelines/_single_steps.py +71 -36
  137. scitex/scholar/storage/_LibraryManager.py +97 -1695
  138. scitex/scholar/storage/_mixins/__init__.py +30 -0
  139. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  140. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  141. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  142. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  143. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  144. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  145. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  146. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  147. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  148. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  149. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  150. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  151. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  152. scitex/security/README.md +3 -3
  153. scitex/session/README.md +1 -1
  154. scitex/sh/README.md +1 -1
  155. scitex/social/__init__.py +153 -0
  156. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  157. scitex/template/README.md +1 -1
  158. scitex/template/clone_writer_directory.py +5 -5
  159. scitex/writer/README.md +1 -1
  160. scitex/writer/_mcp/handlers.py +11 -744
  161. scitex/writer/_mcp/tool_schemas.py +5 -335
  162. scitex-2.15.1.dist-info/METADATA +648 -0
  163. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
  164. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  165. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  166. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  167. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  168. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  169. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  170. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  171. scitex/diagram/_compile.py +0 -312
  172. scitex/diagram/_diagram.py +0 -355
  173. scitex/diagram/_mcp/__init__.py +0 -4
  174. scitex/diagram/_mcp/handlers.py +0 -400
  175. scitex/diagram/_mcp/tool_schemas.py +0 -157
  176. scitex/diagram/_presets.py +0 -173
  177. scitex/diagram/_schema.py +0 -182
  178. scitex/diagram/_split.py +0 -278
  179. scitex/plt/_mcp/__init__.py +0 -4
  180. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  181. scitex/plt/_mcp/_handlers_figure.py +0 -195
  182. scitex/plt/_mcp/_handlers_plot.py +0 -252
  183. scitex/plt/_mcp/_handlers_style.py +0 -219
  184. scitex/plt/_mcp/handlers.py +0 -74
  185. scitex/plt/_mcp/tool_schemas.py +0 -497
  186. scitex/plt/mcp_server.py +0 -231
  187. scitex/scholar/data/.gitkeep +0 -0
  188. scitex/scholar/data/README.md +0 -44
  189. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  190. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  191. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  192. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  193. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  194. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  195. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  196. scitex/scholar/data/bib_files/pac.bib +0 -698
  197. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  198. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  199. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  200. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  201. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  202. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  203. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  204. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  205. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  206. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  207. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  208. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  209. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  210. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  211. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  212. scitex/scholar/data/impact_factor.db +0 -0
  213. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  214. scitex/scholar/examples/dev.py +0 -38
  215. scitex-2.14.0.dist-info/METADATA +0 -1238
  216. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
  217. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
  218. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -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": True,
224
- "message": "Paper fetched",
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": bool(symlink_path),
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_ prefix):
65
+ SCITEX_X_CONSUMER_KEY Twitter API keys
66
+ SCITEX_LINKEDIN_ACCESS_TOKEN LinkedIn OAuth token
67
+ SCITEX_REDDIT_CLIENT_ID Reddit app credentials
68
+ SCITEX_YOUTUBE_API_KEY YouTube API key
69
+
70
+ Note: Falls back to SOCIALIA_ prefix if SCITEX_ 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