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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -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,355 @@
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.group(invoke_without_command=True)
283
+ @click.pass_context
284
+ def mcp(ctx):
285
+ """
286
+ MCP (Model Context Protocol) server operations
287
+
288
+ \b
289
+ Commands:
290
+ start - Start the MCP server
291
+ doctor - Check MCP server health
292
+ list-tools - List available MCP tools
293
+ installation - Show Claude Desktop configuration
294
+
295
+ \b
296
+ Examples:
297
+ scitex social mcp start
298
+ scitex social mcp doctor
299
+ """
300
+ if ctx.invoked_subcommand is None:
301
+ click.echo(ctx.get_help())
302
+
303
+
304
+ @mcp.command()
305
+ def start():
306
+ """
307
+ Start the MCP server
308
+
309
+ \b
310
+ Example:
311
+ scitex social mcp start
312
+ """
313
+ sys.exit(_run_socialia("mcp", "start"))
314
+
315
+
316
+ @mcp.command()
317
+ def doctor():
318
+ """
319
+ Check MCP server health
320
+
321
+ \b
322
+ Example:
323
+ scitex social mcp doctor
324
+ """
325
+ sys.exit(_run_socialia("mcp", "doctor"))
326
+
327
+
328
+ @mcp.command("list-tools")
329
+ def list_tools():
330
+ """
331
+ List available MCP tools
332
+
333
+ \b
334
+ Example:
335
+ scitex social mcp list-tools
336
+ """
337
+ sys.exit(_run_socialia("mcp", "list-tools"))
338
+
339
+
340
+ @mcp.command()
341
+ def installation():
342
+ """
343
+ Show Claude Desktop configuration
344
+
345
+ \b
346
+ Example:
347
+ scitex social mcp installation
348
+ """
349
+ sys.exit(_run_socialia("mcp", "installation"))
350
+
351
+
352
+ if __name__ == "__main__":
353
+ social()
354
+
355
+ # EOF