scitex 2.16.2__py3-none-any.whl → 2.17.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.
- 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 +169 -0
- scitex/_dev/_dashboard/_scripts.py +301 -0
- scitex/_dev/_dashboard/_styles.py +205 -0
- scitex/_dev/_dashboard/_templates.py +117 -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/_ssh.py +332 -0
- scitex/_dev/_versions.py +272 -0
- scitex/_mcp_resources/_cheatsheet.py +1 -1
- scitex/_mcp_resources/_modules.py +1 -1
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/dev.py +186 -0
- scitex/_mcp_tools/verify.py +256 -0
- scitex/audio/_audio_check.py +84 -41
- scitex/cli/capture.py +45 -22
- scitex/cli/dev.py +494 -0
- scitex/cli/main.py +4 -0
- scitex/cli/stats.py +48 -20
- scitex/cli/verify.py +473 -0
- scitex/dev/plt/__init__.py +1 -1
- scitex/dev/plt/mpl/get_dir_ax.py +1 -1
- scitex/dev/plt/mpl/get_signatures.py +1 -1
- scitex/dev/plt/mpl/get_signatures_details.py +1 -1
- scitex/io/_load.py +8 -1
- scitex/io/_save.py +12 -0
- scitex/plt/__init__.py +16 -6
- scitex/session/README.md +2 -2
- scitex/session/__init__.py +1 -0
- scitex/session/_decorator.py +57 -33
- scitex/session/_lifecycle/__init__.py +23 -0
- scitex/session/_lifecycle/_close.py +225 -0
- scitex/session/_lifecycle/_config.py +112 -0
- scitex/session/_lifecycle/_matplotlib.py +83 -0
- scitex/session/_lifecycle/_start.py +246 -0
- scitex/session/_lifecycle/_utils.py +186 -0
- scitex/session/_manager.py +40 -3
- scitex/session/template.py +1 -1
- scitex/template/__init__.py +18 -1
- scitex/template/_templates/plt.py +1 -1
- scitex/template/_templates/session.py +1 -1
- scitex/template/clone_research_minimal.py +111 -0
- scitex/verify/README.md +300 -0
- scitex/verify/__init__.py +208 -0
- scitex/verify/_chain.py +369 -0
- scitex/verify/_db.py +600 -0
- scitex/verify/_hash.py +187 -0
- scitex/verify/_integration.py +127 -0
- scitex/verify/_rerun.py +253 -0
- scitex/verify/_tracker.py +330 -0
- scitex/verify/_visualize.py +44 -0
- scitex/verify/_viz/__init__.py +38 -0
- scitex/verify/_viz/_colors.py +84 -0
- scitex/verify/_viz/_format.py +302 -0
- scitex/verify/_viz/_json.py +192 -0
- scitex/verify/_viz/_mermaid.py +440 -0
- scitex/verify/_viz/_templates.py +246 -0
- scitex/verify/_viz/_utils.py +56 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/METADATA +2 -1
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/RECORD +69 -28
- scitex/session/_lifecycle.py +0 -827
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/WHEEL +0 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-02-01 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/_mcp_tools/verify.py
|
|
4
|
+
"""Verify module tools for FastMCP unified server."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _json(data: dict) -> str:
|
|
13
|
+
return json.dumps(data, indent=2, default=str)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_verify_tools(mcp) -> None:
|
|
17
|
+
"""Register verify tools with FastMCP server."""
|
|
18
|
+
|
|
19
|
+
@mcp.tool()
|
|
20
|
+
async def verify_list(
|
|
21
|
+
limit: int = 50,
|
|
22
|
+
status_filter: Optional[str] = None,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""[verify] List all tracked runs with verification status.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
limit : int, optional
|
|
29
|
+
Maximum number of runs to return (default: 50)
|
|
30
|
+
status_filter : str, optional
|
|
31
|
+
Filter by status: 'success', 'failed', 'running', or None for all
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
str
|
|
36
|
+
JSON with list of runs and their verification status
|
|
37
|
+
"""
|
|
38
|
+
from scitex.verify import get_db, verify_run
|
|
39
|
+
|
|
40
|
+
db = get_db()
|
|
41
|
+
runs = db.list_runs(status=status_filter, limit=limit)
|
|
42
|
+
|
|
43
|
+
results = []
|
|
44
|
+
for run in runs:
|
|
45
|
+
verification = verify_run(run["session_id"])
|
|
46
|
+
results.append(
|
|
47
|
+
{
|
|
48
|
+
"session_id": run["session_id"],
|
|
49
|
+
"script_path": run.get("script_path"),
|
|
50
|
+
"db_status": run.get("status"),
|
|
51
|
+
"verification_status": verification.status.value,
|
|
52
|
+
"is_verified": verification.is_verified,
|
|
53
|
+
"started_at": run.get("started_at"),
|
|
54
|
+
"finished_at": run.get("finished_at"),
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return _json(
|
|
59
|
+
{
|
|
60
|
+
"count": len(results),
|
|
61
|
+
"runs": results,
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
async def verify_run(
|
|
67
|
+
session_or_path: str,
|
|
68
|
+
) -> str:
|
|
69
|
+
"""[verify] Verify a specific session run by checking all file hashes.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
session_or_path : str
|
|
74
|
+
Session ID (e.g., '2025Y-11M-18D-09h12m03s_HmH5') or
|
|
75
|
+
path to a file to find its associated session
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
JSON with verification results including file-level details
|
|
81
|
+
"""
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
|
|
84
|
+
from scitex.verify import get_db
|
|
85
|
+
from scitex.verify import verify_run as do_verify_run
|
|
86
|
+
|
|
87
|
+
db = get_db()
|
|
88
|
+
|
|
89
|
+
# Check if it's a file path
|
|
90
|
+
path = Path(session_or_path)
|
|
91
|
+
if path.exists():
|
|
92
|
+
sessions = db.find_session_by_file(str(path.resolve()), role="output")
|
|
93
|
+
if not sessions:
|
|
94
|
+
sessions = db.find_session_by_file(str(path.resolve()), role="input")
|
|
95
|
+
|
|
96
|
+
if not sessions:
|
|
97
|
+
return _json(
|
|
98
|
+
{
|
|
99
|
+
"error": f"No session found for file: {session_or_path}",
|
|
100
|
+
"session_id": None,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
session_id = sessions[0]
|
|
104
|
+
else:
|
|
105
|
+
session_id = session_or_path
|
|
106
|
+
|
|
107
|
+
verification = do_verify_run(session_id)
|
|
108
|
+
|
|
109
|
+
return _json(
|
|
110
|
+
{
|
|
111
|
+
"session_id": verification.session_id,
|
|
112
|
+
"script_path": verification.script_path,
|
|
113
|
+
"status": verification.status.value,
|
|
114
|
+
"is_verified": verification.is_verified,
|
|
115
|
+
"combined_hash_expected": verification.combined_hash_expected,
|
|
116
|
+
"files": [
|
|
117
|
+
{
|
|
118
|
+
"path": f.path,
|
|
119
|
+
"role": f.role,
|
|
120
|
+
"status": f.status.value,
|
|
121
|
+
"expected_hash": f.expected_hash,
|
|
122
|
+
"current_hash": f.current_hash,
|
|
123
|
+
"is_verified": f.is_verified,
|
|
124
|
+
}
|
|
125
|
+
for f in verification.files
|
|
126
|
+
],
|
|
127
|
+
"mismatched_count": len(verification.mismatched_files),
|
|
128
|
+
"missing_count": len(verification.missing_files),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@mcp.tool()
|
|
133
|
+
async def verify_chain(
|
|
134
|
+
target_file: str,
|
|
135
|
+
) -> str:
|
|
136
|
+
"""[verify] Verify the dependency chain for a target file.
|
|
137
|
+
|
|
138
|
+
Traces back through all sessions that contributed to producing
|
|
139
|
+
the target file and verifies each one.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
target_file : str
|
|
144
|
+
Path to the target file to trace
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
str
|
|
149
|
+
JSON with chain verification results
|
|
150
|
+
"""
|
|
151
|
+
from pathlib import Path
|
|
152
|
+
|
|
153
|
+
from scitex.verify import verify_chain as do_verify_chain
|
|
154
|
+
|
|
155
|
+
path = Path(target_file)
|
|
156
|
+
if not path.exists():
|
|
157
|
+
return _json(
|
|
158
|
+
{
|
|
159
|
+
"error": f"File not found: {target_file}",
|
|
160
|
+
"target_file": target_file,
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
chain = do_verify_chain(str(path.resolve()))
|
|
165
|
+
|
|
166
|
+
return _json(
|
|
167
|
+
{
|
|
168
|
+
"target_file": chain.target_file,
|
|
169
|
+
"status": chain.status.value,
|
|
170
|
+
"is_verified": chain.is_verified,
|
|
171
|
+
"chain_length": len(chain.runs),
|
|
172
|
+
"failed_runs_count": len(chain.failed_runs),
|
|
173
|
+
"runs": [
|
|
174
|
+
{
|
|
175
|
+
"session_id": r.session_id,
|
|
176
|
+
"script_path": r.script_path,
|
|
177
|
+
"status": r.status.value,
|
|
178
|
+
"is_verified": r.is_verified,
|
|
179
|
+
"mismatched_files": [f.path for f in r.mismatched_files],
|
|
180
|
+
"missing_files": [f.path for f in r.missing_files],
|
|
181
|
+
}
|
|
182
|
+
for r in chain.runs
|
|
183
|
+
],
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@mcp.tool()
|
|
188
|
+
async def verify_status() -> str:
|
|
189
|
+
"""[verify] Show verification status summary (like git status).
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
str
|
|
194
|
+
JSON with counts of verified, mismatched, and missing runs
|
|
195
|
+
"""
|
|
196
|
+
from scitex.verify import get_status
|
|
197
|
+
|
|
198
|
+
status = get_status()
|
|
199
|
+
return _json(status)
|
|
200
|
+
|
|
201
|
+
@mcp.tool()
|
|
202
|
+
async def verify_stats() -> str:
|
|
203
|
+
"""[verify] Show verification database statistics.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
str
|
|
208
|
+
JSON with database statistics
|
|
209
|
+
"""
|
|
210
|
+
from scitex.verify import get_db
|
|
211
|
+
|
|
212
|
+
db = get_db()
|
|
213
|
+
stats = db.stats()
|
|
214
|
+
return _json(stats)
|
|
215
|
+
|
|
216
|
+
@mcp.tool()
|
|
217
|
+
async def verify_mermaid(
|
|
218
|
+
session_id: Optional[str] = None,
|
|
219
|
+
target_file: Optional[str] = None,
|
|
220
|
+
) -> str:
|
|
221
|
+
"""[verify] Generate Mermaid diagram for verification DAG.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
session_id : str, optional
|
|
226
|
+
Start from this session
|
|
227
|
+
target_file : str, optional
|
|
228
|
+
Start from session that produced this file
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
str
|
|
233
|
+
Mermaid diagram code
|
|
234
|
+
"""
|
|
235
|
+
from pathlib import Path
|
|
236
|
+
|
|
237
|
+
from scitex.verify import generate_mermaid_dag
|
|
238
|
+
|
|
239
|
+
if target_file:
|
|
240
|
+
target_file = str(Path(target_file).resolve())
|
|
241
|
+
|
|
242
|
+
mermaid_code = generate_mermaid_dag(
|
|
243
|
+
session_id=session_id,
|
|
244
|
+
target_file=target_file,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return _json(
|
|
248
|
+
{
|
|
249
|
+
"mermaid": mermaid_code,
|
|
250
|
+
"session_id": session_id,
|
|
251
|
+
"target_file": target_file,
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# 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")
|