scitex 2.17.0__py3-none-any.whl → 2.17.4__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/_dev/__init__.py +122 -0
- scitex/_dev/_config.py +391 -0
- scitex/_dev/_dashboard/__init__.py +11 -0
- scitex/_dev/_dashboard/_app.py +89 -0
- scitex/_dev/_dashboard/_routes.py +182 -0
- scitex/_dev/_dashboard/_scripts.py +422 -0
- scitex/_dev/_dashboard/_styles.py +295 -0
- scitex/_dev/_dashboard/_templates.py +130 -0
- scitex/_dev/_dashboard/static/version-dashboard-favicon.svg +12 -0
- scitex/_dev/_ecosystem.py +109 -0
- scitex/_dev/_github.py +360 -0
- scitex/_dev/_mcp/__init__.py +11 -0
- scitex/_dev/_mcp/handlers.py +182 -0
- scitex/_dev/_rtd.py +122 -0
- scitex/_dev/_ssh.py +362 -0
- scitex/_dev/_versions.py +272 -0
- scitex/_mcp_tools/__init__.py +2 -0
- scitex/_mcp_tools/dev.py +186 -0
- scitex/audio/_audio_check.py +84 -41
- scitex/cli/capture.py +45 -22
- scitex/cli/dev.py +494 -0
- scitex/cli/main.py +2 -0
- scitex/cli/stats.py +48 -20
- scitex/cli/verify.py +33 -36
- scitex/plt/__init__.py +16 -6
- scitex/scholar/_mcp/crossref_handlers.py +45 -7
- scitex/scholar/_mcp/openalex_handlers.py +45 -7
- scitex/scholar/config/default.yaml +2 -0
- scitex/scholar/local_dbs/__init__.py +5 -1
- scitex/scholar/local_dbs/export.py +93 -0
- scitex/scholar/local_dbs/unified.py +505 -0
- scitex/scholar/metadata_engines/ScholarEngine.py +11 -0
- scitex/scholar/metadata_engines/individual/OpenAlexLocalEngine.py +346 -0
- scitex/scholar/metadata_engines/individual/__init__.py +1 -0
- scitex/template/__init__.py +18 -1
- scitex/template/clone_research_minimal.py +111 -0
- scitex/verify/README.md +0 -12
- scitex/verify/__init__.py +0 -4
- scitex/verify/_visualize.py +0 -4
- scitex/verify/_viz/__init__.py +0 -18
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/METADATA +2 -1
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/RECORD +45 -24
- scitex/verify/_viz/_plotly.py +0 -193
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/WHEEL +0 -0
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/entry_points.txt +0 -0
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/licenses/LICENSE +0 -0
scitex/_mcp_tools/dev.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-02
|
|
3
|
+
# File: scitex/_mcp_tools/dev.py
|
|
4
|
+
|
|
5
|
+
"""MCP tool registration for developer utilities."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _json(obj) -> str:
|
|
13
|
+
"""Serialize object to JSON string."""
|
|
14
|
+
return json.dumps(obj, indent=2, default=str)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_dev_tools(mcp) -> None:
|
|
18
|
+
"""Register developer tools with FastMCP server."""
|
|
19
|
+
|
|
20
|
+
@mcp.tool()
|
|
21
|
+
async def dev_list_versions(
|
|
22
|
+
packages: list[str] | None = None,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""[dev] List versions across the scitex ecosystem.
|
|
25
|
+
|
|
26
|
+
Shows version information from multiple sources:
|
|
27
|
+
- pyproject.toml (local source)
|
|
28
|
+
- installed package (importlib.metadata)
|
|
29
|
+
- git tag (latest version tag)
|
|
30
|
+
- git branch (current branch)
|
|
31
|
+
- PyPI (remote published version)
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
packages : list[str] | None
|
|
36
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
37
|
+
Available packages: scitex, scitex-cloud, scitex-writer, scitex-dataset,
|
|
38
|
+
figrecipe, crossref-local, openalex-local, socialia
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
str
|
|
43
|
+
JSON with version information for each package.
|
|
44
|
+
"""
|
|
45
|
+
from scitex._dev._mcp.handlers import list_versions_handler
|
|
46
|
+
|
|
47
|
+
result = await list_versions_handler(packages)
|
|
48
|
+
return _json(result)
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
async def dev_check_versions(
|
|
52
|
+
packages: list[str] | None = None,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""[dev] Check version consistency across the scitex ecosystem.
|
|
55
|
+
|
|
56
|
+
Checks for version mismatches between sources and returns a summary.
|
|
57
|
+
|
|
58
|
+
Status values:
|
|
59
|
+
- ok: All versions consistent
|
|
60
|
+
- mismatch: Local sources disagree
|
|
61
|
+
- unreleased: Local > PyPI (ready to release)
|
|
62
|
+
- outdated: Local < PyPI (should update)
|
|
63
|
+
- unavailable: Package not found locally
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
packages : list[str] | None
|
|
68
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
str
|
|
73
|
+
JSON with detailed version check results and summary.
|
|
74
|
+
"""
|
|
75
|
+
from scitex._dev._mcp.handlers import check_versions_handler
|
|
76
|
+
|
|
77
|
+
result = await check_versions_handler(packages)
|
|
78
|
+
return _json(result)
|
|
79
|
+
|
|
80
|
+
@mcp.tool()
|
|
81
|
+
async def dev_check_hosts(
|
|
82
|
+
packages: list[str] | None = None,
|
|
83
|
+
hosts: list[str] | None = None,
|
|
84
|
+
) -> str:
|
|
85
|
+
"""[dev] Check package versions on SSH hosts.
|
|
86
|
+
|
|
87
|
+
Connects to configured SSH hosts and checks installed package versions.
|
|
88
|
+
Hosts are configured in ~/.scitex/dev_config.yaml or via SCITEX_DEV_HOSTS.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
packages : list[str] | None
|
|
93
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
94
|
+
hosts : list[str] | None
|
|
95
|
+
List of host names to check. If None, checks all enabled hosts.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
str
|
|
100
|
+
JSON with host -> package -> version mapping.
|
|
101
|
+
"""
|
|
102
|
+
from scitex._dev._mcp.handlers import check_hosts_handler
|
|
103
|
+
|
|
104
|
+
result = await check_hosts_handler(packages, hosts)
|
|
105
|
+
return _json(result)
|
|
106
|
+
|
|
107
|
+
@mcp.tool()
|
|
108
|
+
async def dev_check_remotes(
|
|
109
|
+
packages: list[str] | None = None,
|
|
110
|
+
remotes: list[str] | None = None,
|
|
111
|
+
) -> str:
|
|
112
|
+
"""[dev] Check package versions on GitHub remotes.
|
|
113
|
+
|
|
114
|
+
Fetches tags and releases from configured GitHub organizations.
|
|
115
|
+
Remotes are configured in ~/.scitex/dev_config.yaml.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
packages : list[str] | None
|
|
120
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
121
|
+
remotes : list[str] | None
|
|
122
|
+
List of remote names to check. If None, checks all enabled remotes.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
str
|
|
127
|
+
JSON with remote -> package -> version mapping.
|
|
128
|
+
"""
|
|
129
|
+
from scitex._dev._mcp.handlers import check_remotes_handler
|
|
130
|
+
|
|
131
|
+
result = await check_remotes_handler(packages, remotes)
|
|
132
|
+
return _json(result)
|
|
133
|
+
|
|
134
|
+
@mcp.tool()
|
|
135
|
+
async def dev_get_config() -> str:
|
|
136
|
+
"""[dev] Get current developer configuration.
|
|
137
|
+
|
|
138
|
+
Returns the configuration from ~/.scitex/dev_config.yaml including:
|
|
139
|
+
- Packages to track
|
|
140
|
+
- SSH hosts
|
|
141
|
+
- GitHub remotes
|
|
142
|
+
- Branches to track
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
str
|
|
147
|
+
JSON with current configuration.
|
|
148
|
+
"""
|
|
149
|
+
from scitex._dev._mcp.handlers import get_config_handler
|
|
150
|
+
|
|
151
|
+
result = await get_config_handler()
|
|
152
|
+
return _json(result)
|
|
153
|
+
|
|
154
|
+
@mcp.tool()
|
|
155
|
+
async def dev_full_versions(
|
|
156
|
+
packages: list[str] | None = None,
|
|
157
|
+
include_hosts: bool = False,
|
|
158
|
+
include_remotes: bool = True,
|
|
159
|
+
) -> str:
|
|
160
|
+
"""[dev] Get comprehensive version data from all sources.
|
|
161
|
+
|
|
162
|
+
Combines local, host, and GitHub remote version information.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
packages : list[str] | None
|
|
167
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
168
|
+
include_hosts : bool
|
|
169
|
+
Include SSH host version checks (slower, requires SSH access).
|
|
170
|
+
include_remotes : bool
|
|
171
|
+
Include GitHub remote version checks.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
str
|
|
176
|
+
JSON with combined version data.
|
|
177
|
+
"""
|
|
178
|
+
from scitex._dev._mcp.handlers import get_full_versions_handler
|
|
179
|
+
|
|
180
|
+
result = await get_full_versions_handler(
|
|
181
|
+
packages, include_hosts, include_remotes
|
|
182
|
+
)
|
|
183
|
+
return _json(result)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# EOF
|
scitex/audio/_audio_check.py
CHANGED
|
@@ -7,9 +7,75 @@ before attempting to play audio.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
10
12
|
import subprocess
|
|
11
13
|
|
|
12
|
-
__all__ = ["check_local_audio_available"]
|
|
14
|
+
__all__ = ["check_local_audio_available", "check_wsl_windows_audio_available"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_wsl_windows_audio_available() -> dict:
|
|
18
|
+
"""Check if WSL Windows audio playback is available.
|
|
19
|
+
|
|
20
|
+
In WSL, audio can be played via PowerShell's System.Media.SoundPlayer
|
|
21
|
+
even when PulseAudio is unavailable or SUSPENDED.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
dict with keys:
|
|
26
|
+
- available: bool - True if Windows playback via PowerShell is possible
|
|
27
|
+
- reason: str - Human-readable explanation
|
|
28
|
+
"""
|
|
29
|
+
if not os.path.exists("/mnt/c/Windows"):
|
|
30
|
+
return {"available": False, "reason": "Not running in WSL"}
|
|
31
|
+
|
|
32
|
+
if not shutil.which("powershell.exe"):
|
|
33
|
+
return {"available": False, "reason": "powershell.exe not found in PATH"}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"available": True,
|
|
37
|
+
"reason": "WSL Windows playback available via PowerShell",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _try_wsl_fallback(
|
|
42
|
+
state: str, reason: str, pulseaudio_state: str | None = None
|
|
43
|
+
) -> dict:
|
|
44
|
+
"""Try WSL Windows fallback, return appropriate result dict."""
|
|
45
|
+
wsl_check = check_wsl_windows_audio_available()
|
|
46
|
+
if wsl_check["available"]:
|
|
47
|
+
result = {
|
|
48
|
+
"available": True,
|
|
49
|
+
"state": "WSL_WINDOWS",
|
|
50
|
+
"reason": wsl_check["reason"],
|
|
51
|
+
"fallback": "windows_powershell",
|
|
52
|
+
}
|
|
53
|
+
if pulseaudio_state:
|
|
54
|
+
result["pulseaudio_state"] = pulseaudio_state
|
|
55
|
+
return result
|
|
56
|
+
return {"available": False, "state": state, "reason": reason}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _parse_pulseaudio_state(output: str) -> dict:
|
|
60
|
+
"""Parse PulseAudio sink state from pactl output."""
|
|
61
|
+
for line in output.strip().split("\n"):
|
|
62
|
+
parts = line.split("\t")
|
|
63
|
+
if len(parts) >= 5:
|
|
64
|
+
state = parts[4]
|
|
65
|
+
if state == "SUSPENDED":
|
|
66
|
+
return _try_wsl_fallback(
|
|
67
|
+
"SUSPENDED",
|
|
68
|
+
"Audio sink SUSPENDED (no active output device)",
|
|
69
|
+
pulseaudio_state="SUSPENDED",
|
|
70
|
+
)
|
|
71
|
+
if state in ("RUNNING", "IDLE"):
|
|
72
|
+
return {
|
|
73
|
+
"available": True,
|
|
74
|
+
"state": state,
|
|
75
|
+
"reason": f"Audio sink is {state}",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return _try_wsl_fallback("UNKNOWN", "Could not determine sink state")
|
|
13
79
|
|
|
14
80
|
|
|
15
81
|
def check_local_audio_available() -> dict:
|
|
@@ -18,11 +84,15 @@ def check_local_audio_available() -> dict:
|
|
|
18
84
|
Checks PulseAudio sink state to determine if audio can actually be heard.
|
|
19
85
|
On NAS or headless servers, the sink is typically SUSPENDED.
|
|
20
86
|
|
|
21
|
-
|
|
87
|
+
In WSL environments, also checks for Windows playback fallback via PowerShell.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
22
91
|
dict with keys:
|
|
23
92
|
- available: bool - True if local audio output is likely to work
|
|
24
93
|
- state: str - 'RUNNING', 'IDLE', 'SUSPENDED', 'NO_SINK', etc.
|
|
25
94
|
- reason: str - Human-readable explanation
|
|
95
|
+
- fallback: str (optional) - Fallback method if primary unavailable
|
|
26
96
|
"""
|
|
27
97
|
try:
|
|
28
98
|
result = subprocess.run(
|
|
@@ -32,45 +102,22 @@ def check_local_audio_available() -> dict:
|
|
|
32
102
|
timeout=5,
|
|
33
103
|
)
|
|
34
104
|
if result.returncode != 0:
|
|
35
|
-
return
|
|
36
|
-
"available": False,
|
|
37
|
-
"state": "NO_PACTL",
|
|
38
|
-
"reason": "PulseAudio not available",
|
|
39
|
-
}
|
|
105
|
+
return _try_wsl_fallback("NO_PACTL", "PulseAudio not available")
|
|
40
106
|
|
|
41
107
|
if not result.stdout.strip():
|
|
42
|
-
return
|
|
43
|
-
"available": False,
|
|
44
|
-
"state": "NO_SINK",
|
|
45
|
-
"reason": "No audio sinks found",
|
|
46
|
-
}
|
|
108
|
+
return _try_wsl_fallback("NO_SINK", "No audio sinks found")
|
|
47
109
|
|
|
48
|
-
|
|
49
|
-
for line in result.stdout.strip().split("\n"):
|
|
50
|
-
parts = line.split("\t")
|
|
51
|
-
if len(parts) >= 5:
|
|
52
|
-
state = parts[4]
|
|
53
|
-
if state == "SUSPENDED":
|
|
54
|
-
return {
|
|
55
|
-
"available": False,
|
|
56
|
-
"state": "SUSPENDED",
|
|
57
|
-
"reason": "Audio sink SUSPENDED (no active output device)",
|
|
58
|
-
}
|
|
59
|
-
elif state in ("RUNNING", "IDLE"):
|
|
60
|
-
return {
|
|
61
|
-
"available": True,
|
|
62
|
-
"state": state,
|
|
63
|
-
"reason": f"Audio sink is {state}",
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
"available": False,
|
|
68
|
-
"state": "UNKNOWN",
|
|
69
|
-
"reason": "Could not determine sink state",
|
|
70
|
-
}
|
|
110
|
+
return _parse_pulseaudio_state(result.stdout)
|
|
71
111
|
|
|
72
112
|
except FileNotFoundError:
|
|
73
|
-
|
|
113
|
+
wsl_check = check_wsl_windows_audio_available()
|
|
114
|
+
if wsl_check["available"]:
|
|
115
|
+
return {
|
|
116
|
+
"available": True,
|
|
117
|
+
"state": "WSL_WINDOWS",
|
|
118
|
+
"reason": wsl_check["reason"],
|
|
119
|
+
"fallback": "windows_powershell",
|
|
120
|
+
}
|
|
74
121
|
return {
|
|
75
122
|
"available": True,
|
|
76
123
|
"state": "NO_PACTL",
|
|
@@ -83,11 +130,7 @@ def check_local_audio_available() -> dict:
|
|
|
83
130
|
"reason": "PulseAudio query timed out",
|
|
84
131
|
}
|
|
85
132
|
except Exception as e:
|
|
86
|
-
return {
|
|
87
|
-
"available": False,
|
|
88
|
-
"state": "ERROR",
|
|
89
|
-
"reason": str(e),
|
|
90
|
-
}
|
|
133
|
+
return {"available": False, "state": "ERROR", "reason": str(e)}
|
|
91
134
|
|
|
92
135
|
|
|
93
136
|
# EOF
|
scitex/cli/capture.py
CHANGED
|
@@ -406,32 +406,55 @@ def doctor():
|
|
|
406
406
|
|
|
407
407
|
|
|
408
408
|
@mcp.command("list-tools")
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
List available MCP tools
|
|
412
|
-
|
|
413
|
-
\b
|
|
414
|
-
Example:
|
|
415
|
-
scitex capture mcp list-tools
|
|
416
|
-
"""
|
|
409
|
+
@click.option("-v", "--verbose", count=True, help="-v params, -vv returns")
|
|
410
|
+
def list_tools(verbose):
|
|
411
|
+
"""List available MCP tools for capture."""
|
|
417
412
|
click.secho("Capture MCP Tools", fg="cyan", bold=True)
|
|
418
413
|
click.echo()
|
|
414
|
+
# (name, desc, params, returns)
|
|
419
415
|
tools = [
|
|
420
|
-
(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
416
|
+
(
|
|
417
|
+
"capture_screenshot",
|
|
418
|
+
"Capture screenshot",
|
|
419
|
+
"output_path=None, monitor=0",
|
|
420
|
+
"str",
|
|
421
|
+
),
|
|
422
|
+
(
|
|
423
|
+
"capture_window",
|
|
424
|
+
"Capture specific window",
|
|
425
|
+
"window_id: str, output=None",
|
|
426
|
+
"str",
|
|
427
|
+
),
|
|
428
|
+
(
|
|
429
|
+
"start_monitoring",
|
|
430
|
+
"Start continuous capture",
|
|
431
|
+
"interval=5.0, monitor=0",
|
|
432
|
+
"str",
|
|
433
|
+
),
|
|
434
|
+
("stop_monitoring", "Stop monitoring", "session_id=None", "str"),
|
|
435
|
+
("get_monitoring_status", "Get monitoring status", "", "JSON"),
|
|
436
|
+
(
|
|
437
|
+
"analyze_screenshot",
|
|
438
|
+
"Analyze screenshot for errors",
|
|
439
|
+
"image_path: str",
|
|
440
|
+
"JSON",
|
|
441
|
+
),
|
|
442
|
+
("list_recent_screenshots", "List recent screenshots", "limit=10", "JSON"),
|
|
443
|
+
("clear_cache", "Clear screenshot cache", "older_than_hours=24", "JSON"),
|
|
444
|
+
("create_gif", "Create animated GIF", "session_id: str, output=None", "str"),
|
|
445
|
+
("list_sessions", "List monitoring sessions", "", "JSON"),
|
|
446
|
+
("get_info", "Get monitor/window info", "", "JSON"),
|
|
447
|
+
("list_windows", "List visible windows", "", "JSON"),
|
|
432
448
|
]
|
|
433
|
-
for name, desc in tools:
|
|
434
|
-
click.
|
|
449
|
+
for name, desc, params, returns in tools:
|
|
450
|
+
click.secho(f" capture_{name}", fg="green", bold=True, nl=False)
|
|
451
|
+
click.echo(f": {desc}")
|
|
452
|
+
if verbose >= 1 and params:
|
|
453
|
+
click.echo(f" params: {params}")
|
|
454
|
+
if verbose >= 2:
|
|
455
|
+
click.echo(f" returns: {returns}")
|
|
456
|
+
if verbose >= 1:
|
|
457
|
+
click.echo()
|
|
435
458
|
|
|
436
459
|
|
|
437
460
|
@capture.command("list-python-apis")
|