dsh-math-modeling-agent 0.2.8 → 0.4.1

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.
@@ -0,0 +1,396 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$PythonCommand = 'python',
4
+ [string]$SageCommand = '',
5
+ [string]$WslDistro = '',
6
+ [string]$WslSageCommand = 'sage',
7
+ [string]$PrimecountCommand = '',
8
+ [ValidateRange(100, 300000)][int]$TimeoutMilliseconds = 20000
9
+ )
10
+
11
+ $ErrorActionPreference = 'Stop'
12
+
13
+ function Resolve-CommandPath {
14
+ param([Parameter(Mandatory = $true)][string]$Name)
15
+
16
+ if (Test-Path -LiteralPath $Name -PathType Leaf) {
17
+ return (Get-Item -LiteralPath $Name).FullName
18
+ }
19
+
20
+ $command = Get-Command -Name $Name -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
21
+ if ($null -eq $command) {
22
+ return $null
23
+ }
24
+ return $command.Source
25
+ }
26
+
27
+ function Limit-ProbeOutput {
28
+ param([AllowNull()][object]$Text)
29
+
30
+ $value = if ($null -eq $Text) { '' } else { [string]$Text }
31
+ $limit = 65536
32
+ if ($value.Length -gt $limit) {
33
+ return $value.Substring(0, $limit) + [Environment]::NewLine + '[output truncated]'
34
+ }
35
+ return $value
36
+ }
37
+
38
+ function Get-CompletedText {
39
+ param([Parameter(Mandatory = $true)][object]$Task)
40
+
41
+ if ($Task.Wait(1000)) {
42
+ return [string]$Task.Result
43
+ }
44
+ return ''
45
+ }
46
+
47
+ function Invoke-ProcessProbe {
48
+ param(
49
+ [Parameter(Mandatory = $true)][string]$Executable,
50
+ [Parameter(Mandatory = $true)][string[]]$Arguments
51
+ )
52
+
53
+ $startInfo = [Diagnostics.ProcessStartInfo]::new()
54
+ $startInfo.FileName = $Executable
55
+ $startInfo.UseShellExecute = $false
56
+ $startInfo.RedirectStandardOutput = $true
57
+ $startInfo.RedirectStandardError = $true
58
+ foreach ($argument in $Arguments) {
59
+ [void]$startInfo.ArgumentList.Add($argument)
60
+ }
61
+
62
+ $process = [Diagnostics.Process]::new()
63
+ $timedOut = $false
64
+ try {
65
+ $process.StartInfo = $startInfo
66
+ if (-not $process.Start()) {
67
+ throw "Could not start probe: $Executable"
68
+ }
69
+ $stdoutTask = $process.StandardOutput.ReadToEndAsync()
70
+ $stderrTask = $process.StandardError.ReadToEndAsync()
71
+ if (-not $process.WaitForExit($TimeoutMilliseconds)) {
72
+ $timedOut = $true
73
+ try { $process.Kill($true) } catch { }
74
+ [void]$process.WaitForExit(1000)
75
+ }
76
+ $stdout = Limit-ProbeOutput (Get-CompletedText $stdoutTask)
77
+ $stderr = Limit-ProbeOutput (Get-CompletedText $stderrTask)
78
+ $combined = ((@($stdout, $stderr) | Where-Object { $_ }) -join [Environment]::NewLine).Trim()
79
+ if ($timedOut) {
80
+ return [ordered]@{
81
+ status = 'probe_failed'
82
+ stdout = $stdout
83
+ stderr = $stderr
84
+ version_output = $combined
85
+ exit_code = $null
86
+ error = "probe timed out after $TimeoutMilliseconds ms"
87
+ }
88
+ }
89
+ $exitCode = $process.ExitCode
90
+ return [ordered]@{
91
+ status = if ($exitCode -eq 0) { 'available' } else { 'probe_failed' }
92
+ stdout = $stdout
93
+ stderr = $stderr
94
+ version_output = $combined
95
+ exit_code = $exitCode
96
+ error = if ($exitCode -eq 0) { '' } else { $combined }
97
+ }
98
+ }
99
+ catch {
100
+ return [ordered]@{
101
+ status = 'probe_failed'
102
+ stdout = ''
103
+ stderr = ''
104
+ version_output = ''
105
+ exit_code = $null
106
+ error = $_.Exception.Message
107
+ }
108
+ }
109
+ finally {
110
+ $process.Dispose()
111
+ }
112
+ }
113
+
114
+ function Invoke-VersionProbe {
115
+ param(
116
+ [Parameter(Mandatory = $true)][string]$Executable,
117
+ [Parameter(Mandatory = $true)][string[]]$Arguments
118
+ )
119
+
120
+ return Invoke-ProcessProbe -Executable $Executable -Arguments $Arguments
121
+ }
122
+
123
+ function Get-HostIdentity {
124
+ $system = if ([Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::Windows)) {
125
+ 'Windows'
126
+ }
127
+ elseif ([Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([Runtime.InteropServices.OSPlatform]::OSX)) {
128
+ 'Darwin'
129
+ }
130
+ else {
131
+ 'Linux'
132
+ }
133
+ $rawArchitecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
134
+ $architecture = switch ($rawArchitecture) {
135
+ 'x64' { 'x86_64' }
136
+ 'amd64' { 'x86_64' }
137
+ 'arm64' { 'arm64' }
138
+ 'x86' { 'x86' }
139
+ default { $rawArchitecture }
140
+ }
141
+ return [ordered]@{ system = $system; architecture = $architecture; powershell_edition = $PSVersionTable.PSEdition }
142
+ }
143
+
144
+ $wolframInstallations = @()
145
+ $programRoots = @()
146
+ if ($env:ProgramFiles) {
147
+ $programRoots += (Join-Path $env:ProgramFiles 'Wolfram Research\Wolfram')
148
+ }
149
+ if (${env:ProgramFiles(x86)}) {
150
+ $programRoots += (Join-Path ${env:ProgramFiles(x86)} 'Wolfram Research\Wolfram')
151
+ }
152
+
153
+ foreach ($programRoot in ($programRoots | Select-Object -Unique)) {
154
+ if (-not (Test-Path -LiteralPath $programRoot -PathType Container)) {
155
+ continue
156
+ }
157
+ foreach ($versionDirectory in (Get-ChildItem -LiteralPath $programRoot -Directory -ErrorAction SilentlyContinue)) {
158
+ $wolframExecutable = Join-Path $versionDirectory.FullName 'wolfram.exe'
159
+ if (Test-Path -LiteralPath $wolframExecutable -PathType Leaf) {
160
+ $item = Get-Item -LiteralPath $wolframExecutable
161
+ $wolframInstallations += [ordered]@{
162
+ version_directory = $versionDirectory.Name
163
+ executable = $item.FullName
164
+ file_version = $item.VersionInfo.FileVersion
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ $wolframScriptPath = Resolve-CommandPath -Name 'wolframscript'
171
+ if (-not $wolframScriptPath -and $env:ProgramFiles) {
172
+ $knownWolframScript = Join-Path $env:ProgramFiles 'Wolfram Research\WolframScript\wolframscript.exe'
173
+ if (Test-Path -LiteralPath $knownWolframScript -PathType Leaf) {
174
+ $wolframScriptPath = (Get-Item -LiteralPath $knownWolframScript).FullName
175
+ }
176
+ }
177
+
178
+ $wolframScript = if ($wolframScriptPath) {
179
+ $probe = Invoke-VersionProbe -Executable $wolframScriptPath -Arguments @('-version')
180
+ [ordered]@{
181
+ status = $probe.status
182
+ path = $wolframScriptPath
183
+ version_output = $probe.version_output
184
+ exit_code = $probe.exit_code
185
+ error = $probe.error
186
+ }
187
+ }
188
+ else {
189
+ [ordered]@{
190
+ status = 'unavailable'
191
+ path = $null
192
+ version_output = ''
193
+ exit_code = $null
194
+ error = ''
195
+ }
196
+ }
197
+
198
+ $primecountRequestedCommand = ''
199
+ $primecountDiscoverySource = ''
200
+ $primecountPath = $null
201
+ if ($PrimecountCommand) {
202
+ $primecountRequestedCommand = $PrimecountCommand
203
+ $primecountDiscoverySource = 'explicit'
204
+ $primecountPath = Resolve-CommandPath -Name $PrimecountCommand
205
+ }
206
+ elseif ($env:PRIMECOUNT_EXE) {
207
+ $primecountRequestedCommand = $env:PRIMECOUNT_EXE
208
+ $primecountDiscoverySource = 'environment'
209
+ $primecountPath = Resolve-CommandPath -Name $env:PRIMECOUNT_EXE
210
+ }
211
+ else {
212
+ $primecountRequestedCommand = 'primecount'
213
+ $primecountDiscoverySource = 'path'
214
+ $primecountPath = Resolve-CommandPath -Name 'primecount'
215
+ if (-not $primecountPath -and $env:LOCALAPPDATA) {
216
+ $knownPrimecount = Join-Path $env:LOCALAPPDATA 'Programs\primecount\primecount.exe'
217
+ if (Test-Path -LiteralPath $knownPrimecount -PathType Leaf) {
218
+ $primecountPath = (Get-Item -LiteralPath $knownPrimecount).FullName
219
+ $primecountDiscoverySource = 'known_user_location'
220
+ }
221
+ }
222
+ }
223
+
224
+ $primecount = if ($primecountPath) {
225
+ $probe = Invoke-VersionProbe -Executable $primecountPath -Arguments @('--version')
226
+ [ordered]@{
227
+ status = $probe.status
228
+ requested_command = $primecountRequestedCommand
229
+ discovery_source = $primecountDiscoverySource
230
+ path = $primecountPath
231
+ version_output = $probe.version_output
232
+ exit_code = $probe.exit_code
233
+ error = $probe.error
234
+ }
235
+ }
236
+ else {
237
+ [ordered]@{
238
+ status = 'unavailable'
239
+ requested_command = $primecountRequestedCommand
240
+ discovery_source = $primecountDiscoverySource
241
+ path = $null
242
+ version_output = ''
243
+ exit_code = $null
244
+ error = ''
245
+ }
246
+ }
247
+
248
+ $nativeSageName = if ($SageCommand) { $SageCommand } else { 'sage' }
249
+ $nativeSagePath = Resolve-CommandPath -Name $nativeSageName
250
+ $nativeSage = if ($nativeSagePath) {
251
+ $probe = Invoke-VersionProbe -Executable $nativeSagePath -Arguments @('--version')
252
+ [ordered]@{
253
+ status = $probe.status
254
+ requested_command = $nativeSageName
255
+ path = $nativeSagePath
256
+ version_output = $probe.version_output
257
+ exit_code = $probe.exit_code
258
+ error = $probe.error
259
+ }
260
+ }
261
+ else {
262
+ [ordered]@{
263
+ status = 'unavailable'
264
+ requested_command = $nativeSageName
265
+ path = $null
266
+ version_output = ''
267
+ exit_code = $null
268
+ error = ''
269
+ }
270
+ }
271
+
272
+ $wslSage = [ordered]@{
273
+ status = 'not_requested'
274
+ distro = $WslDistro
275
+ requested_command = $WslSageCommand
276
+ version_output = ''
277
+ exit_code = $null
278
+ error = ''
279
+ }
280
+ if ($WslDistro) {
281
+ $wslPath = Resolve-CommandPath -Name 'wsl.exe'
282
+ if (-not $wslPath) {
283
+ $wslSage.status = 'wsl_unavailable'
284
+ }
285
+ else {
286
+ $probe = Invoke-ProcessProbe -Executable $wslPath -Arguments @('-d', $WslDistro, '--', $WslSageCommand, '--version')
287
+ $wslSage.status = $probe.status
288
+ $wslSage.version_output = $probe.version_output
289
+ $wslSage.exit_code = $probe.exit_code
290
+ $wslSage.error = $probe.error
291
+ }
292
+ }
293
+
294
+ $pythonPath = Resolve-CommandPath -Name $PythonCommand
295
+ $pythonProbe = if ($pythonPath) {
296
+ $probeCode = @'
297
+ import importlib.metadata
298
+ import importlib.util
299
+ import json
300
+ import sys
301
+
302
+ modules = {
303
+ "numpy": "numpy",
304
+ "sympy": "sympy",
305
+ "scipy": "scipy",
306
+ "mpmath": "mpmath",
307
+ "sage": "sagemath-standard",
308
+ "sageall": "sagemath-standard",
309
+ }
310
+ libraries = {}
311
+ for module_name, distribution_name in modules.items():
312
+ available = importlib.util.find_spec(module_name) is not None
313
+ version = None
314
+ if available:
315
+ try:
316
+ version = importlib.metadata.version(distribution_name)
317
+ except importlib.metadata.PackageNotFoundError:
318
+ version = None
319
+ libraries[module_name] = {"available": available, "version": version}
320
+ print(json.dumps({
321
+ "python_version": sys.version.split()[0],
322
+ "executable": sys.executable,
323
+ "libraries": libraries,
324
+ }, ensure_ascii=False))
325
+ '@
326
+ $probe = Invoke-ProcessProbe -Executable $pythonPath -Arguments @('-c', $probeCode)
327
+ if ($probe.status -ne 'available') {
328
+ [ordered]@{
329
+ status = 'probe_failed'
330
+ requested_command = $PythonCommand
331
+ path = $pythonPath
332
+ version = ''
333
+ libraries = [ordered]@{}
334
+ exit_code = $probe.exit_code
335
+ error = $probe.error
336
+ }
337
+ }
338
+ else {
339
+ try {
340
+ $data = $probe.stdout | ConvertFrom-Json -AsHashtable
341
+ [ordered]@{
342
+ status = 'available'
343
+ requested_command = $PythonCommand
344
+ path = $data.executable
345
+ version = $data.python_version
346
+ libraries = $data.libraries
347
+ exit_code = 0
348
+ error = ''
349
+ }
350
+ }
351
+ catch {
352
+ [ordered]@{
353
+ status = 'probe_failed'
354
+ requested_command = $PythonCommand
355
+ path = $pythonPath
356
+ version = ''
357
+ libraries = [ordered]@{}
358
+ exit_code = $probe.exit_code
359
+ error = $_.Exception.Message
360
+ }
361
+ }
362
+ }
363
+ }
364
+ else {
365
+ [ordered]@{
366
+ status = 'unavailable'
367
+ requested_command = $PythonCommand
368
+ path = $null
369
+ version = ''
370
+ libraries = [ordered]@{}
371
+ exit_code = $null
372
+ error = ''
373
+ }
374
+ }
375
+
376
+ $result = [ordered]@{
377
+ schema_version = '1.0'
378
+ probed_at_utc = [DateTime]::UtcNow.ToString('o')
379
+ host = Get-HostIdentity
380
+ mathematica = [ordered]@{
381
+ installations = $wolframInstallations
382
+ wolframscript = $wolframScript
383
+ mcp = [ordered]@{
384
+ status = 'requires_agent_probe'
385
+ evidence = 'Call the configured Mathematica MCP from the agent and record the returned Wolfram Language version.'
386
+ }
387
+ }
388
+ primecount = $primecount
389
+ sagemath = [ordered]@{
390
+ native = $nativeSage
391
+ wsl = $wslSage
392
+ }
393
+ python = $pythonProbe
394
+ }
395
+
396
+ $result | ConvertTo-Json -Depth 10 -Compress
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env python3
2
+ """Cross-platform local backend probe for math-science-computation."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import importlib.metadata
8
+ import importlib.util
9
+ import json
10
+ import os
11
+ import platform
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+
19
+ def utc_now() -> str:
20
+ return datetime.now(timezone.utc).isoformat()
21
+
22
+
23
+ def normalized_architecture() -> str:
24
+ value = platform.machine().lower()
25
+ return {
26
+ "amd64": "x86_64",
27
+ "x64": "x86_64",
28
+ "aarch64": "arm64",
29
+ "arm64": "arm64",
30
+ "i386": "x86",
31
+ "i686": "x86",
32
+ "x86": "x86",
33
+ }.get(value, value or "unknown")
34
+
35
+
36
+ def resolve_command(name: str) -> str | None:
37
+ candidate = Path(name).expanduser()
38
+ if candidate.is_file():
39
+ return str(candidate.resolve())
40
+ return shutil.which(name)
41
+
42
+
43
+ def version_probe(executable: str, arguments: list[str]) -> dict:
44
+ try:
45
+ process = subprocess.run(
46
+ [executable, *arguments],
47
+ text=True,
48
+ capture_output=True,
49
+ check=False,
50
+ timeout=20,
51
+ )
52
+ output = "\n".join(part.strip() for part in (process.stdout, process.stderr) if part.strip())
53
+ return {
54
+ "status": "available" if process.returncode == 0 else "probe_failed",
55
+ "version_output": output,
56
+ "exit_code": process.returncode,
57
+ "error": "",
58
+ }
59
+ except (OSError, subprocess.SubprocessError) as exc:
60
+ return {
61
+ "status": "probe_failed",
62
+ "version_output": "",
63
+ "exit_code": None,
64
+ "error": f"{type(exc).__name__}: {exc}",
65
+ }
66
+
67
+
68
+ def known_wolfram_installations(system: str) -> list[dict]:
69
+ candidates: list[Path] = []
70
+ if system == "Windows":
71
+ for variable in ("ProgramFiles", "ProgramFiles(x86)"):
72
+ root = os.environ.get(variable)
73
+ if root:
74
+ candidates.extend(Path(root, "Wolfram Research", "Wolfram").glob("*/Wolfram.exe"))
75
+ elif system == "Darwin":
76
+ applications = Path("/Applications")
77
+ for pattern in ("Mathematica*.app/Contents/MacOS/WolframKernel", "Wolfram*.app/Contents/MacOS/WolframKernel"):
78
+ candidates.extend(applications.glob(pattern))
79
+ elif system == "Linux":
80
+ for root in (Path("/usr/local/Wolfram"), Path("/opt/Wolfram")):
81
+ candidates.extend(root.glob("Mathematica/*/Executables/WolframKernel"))
82
+ candidates.extend(root.glob("Wolfram/*/Executables/WolframKernel"))
83
+
84
+ for command in ("WolframKernel", "wolfram", "math"):
85
+ path = resolve_command(command)
86
+ if path:
87
+ candidates.append(Path(path))
88
+
89
+ installations: list[dict] = []
90
+ seen: set[str] = set()
91
+ for candidate in candidates:
92
+ if not candidate.is_file():
93
+ continue
94
+ resolved = str(candidate.resolve())
95
+ key = os.path.normcase(resolved)
96
+ if key in seen:
97
+ continue
98
+ seen.add(key)
99
+ installations.append(
100
+ {
101
+ "version_directory": candidate.parent.name,
102
+ "executable": resolved,
103
+ "file_version": None,
104
+ }
105
+ )
106
+ return installations
107
+
108
+
109
+ def probe_wolframscript(system: str) -> dict:
110
+ path = resolve_command("wolframscript")
111
+ if not path and system == "Windows":
112
+ root = os.environ.get("ProgramFiles")
113
+ candidate = Path(root, "Wolfram Research", "WolframScript", "wolframscript.exe") if root else None
114
+ if candidate and candidate.is_file():
115
+ path = str(candidate.resolve())
116
+ if not path:
117
+ return {"status": "unavailable", "path": None, "version_output": "", "exit_code": None, "error": ""}
118
+ result = version_probe(path, ["-version"])
119
+ return {"path": path, **result}
120
+
121
+
122
+ def probe_primecount(explicit: str) -> dict:
123
+ if explicit:
124
+ requested, source = explicit, "explicit"
125
+ elif os.environ.get("PRIMECOUNT_EXE"):
126
+ requested, source = os.environ["PRIMECOUNT_EXE"], "environment"
127
+ else:
128
+ requested, source = "primecount", "path"
129
+ path = resolve_command(requested)
130
+ if not path and source == "path" and platform.system() == "Windows":
131
+ local_app_data = os.environ.get("LOCALAPPDATA")
132
+ candidate = Path(local_app_data, "Programs", "primecount", "primecount.exe") if local_app_data else None
133
+ if candidate and candidate.is_file():
134
+ path, source = str(candidate.resolve()), "known_user_location"
135
+ base = {"requested_command": requested, "discovery_source": source, "path": path}
136
+ if not path:
137
+ return {"status": "unavailable", **base, "version_output": "", "exit_code": None, "error": ""}
138
+ return {**base, **version_probe(path, ["--version"])}
139
+
140
+
141
+ def probe_sage(command: str, wsl_distro: str, wsl_sage_command: str, system: str) -> dict:
142
+ requested = command or "sage"
143
+ path = resolve_command(requested)
144
+ native = {"requested_command": requested, "path": path}
145
+ if path:
146
+ native.update(version_probe(path, ["--version"]))
147
+ else:
148
+ native.update({"status": "unavailable", "version_output": "", "exit_code": None, "error": ""})
149
+
150
+ wsl = {
151
+ "status": "not_requested",
152
+ "distro": wsl_distro,
153
+ "requested_command": wsl_sage_command,
154
+ "version_output": "",
155
+ "exit_code": None,
156
+ "error": "",
157
+ }
158
+ if wsl_distro:
159
+ if system != "Windows":
160
+ wsl["status"] = "unsupported_platform"
161
+ else:
162
+ wsl_path = resolve_command("wsl.exe")
163
+ if not wsl_path:
164
+ wsl["status"] = "wsl_unavailable"
165
+ else:
166
+ result = version_probe(wsl_path, ["-d", wsl_distro, "--", wsl_sage_command, "--version"])
167
+ wsl.update(result)
168
+ return {"native": native, "wsl": wsl}
169
+
170
+
171
+ def probe_python(command: str) -> dict:
172
+ path = resolve_command(command)
173
+ base = {"requested_command": command, "path": path}
174
+ if not path:
175
+ return {"status": "unavailable", **base, "version": "", "libraries": {}, "exit_code": None, "error": ""}
176
+ probe_code = r'''
177
+ import importlib.metadata, importlib.util, json, sys
178
+ modules = {"numpy":"numpy","sympy":"sympy","scipy":"scipy","mpmath":"mpmath","sage":"sagemath-standard","sageall":"sagemath-standard"}
179
+ libraries = {}
180
+ for module_name, distribution_name in modules.items():
181
+ available = importlib.util.find_spec(module_name) is not None
182
+ version = None
183
+ if available:
184
+ try: version = importlib.metadata.version(distribution_name)
185
+ except importlib.metadata.PackageNotFoundError: pass
186
+ libraries[module_name] = {"available": available, "version": version}
187
+ print(json.dumps({"python_version":sys.version.split()[0],"executable":sys.executable,"libraries":libraries}, ensure_ascii=False))
188
+ '''
189
+ try:
190
+ process = subprocess.run([path, "-c", probe_code], text=True, capture_output=True, check=False, timeout=20)
191
+ if process.returncode != 0:
192
+ return {"status": "probe_failed", **base, "version": "", "libraries": {}, "exit_code": process.returncode, "error": (process.stderr or process.stdout).strip()}
193
+ data = json.loads(process.stdout)
194
+ return {"status": "available", "requested_command": command, "path": data["executable"], "version": data["python_version"], "libraries": data["libraries"], "exit_code": 0, "error": ""}
195
+ except (OSError, subprocess.SubprocessError, json.JSONDecodeError) as exc:
196
+ return {"status": "probe_failed", **base, "version": "", "libraries": {}, "exit_code": None, "error": f"{type(exc).__name__}: {exc}"}
197
+
198
+
199
+ def build_parser() -> argparse.ArgumentParser:
200
+ parser = argparse.ArgumentParser(description=__doc__)
201
+ parser.add_argument("--python-command", default=sys.executable)
202
+ parser.add_argument("--sage-command", default="")
203
+ parser.add_argument("--wsl-distro", default="")
204
+ parser.add_argument("--wsl-sage-command", default="sage")
205
+ parser.add_argument("--primecount-command", default="")
206
+ return parser
207
+
208
+
209
+ def main() -> int:
210
+ args = build_parser().parse_args()
211
+ system = platform.system()
212
+ result = {
213
+ "schema_version": "1.0",
214
+ "probed_at_utc": utc_now(),
215
+ "host": {"system": system, "architecture": normalized_architecture(), "python_implementation": platform.python_implementation()},
216
+ "mathematica": {
217
+ "installations": known_wolfram_installations(system),
218
+ "wolframscript": probe_wolframscript(system),
219
+ "mcp": {"status": "requires_agent_probe", "evidence": "Call the configured Mathematica MCP from the agent and record the returned Wolfram Language version."},
220
+ },
221
+ "primecount": probe_primecount(args.primecount_command),
222
+ "sagemath": probe_sage(args.sage_command, args.wsl_distro, args.wsl_sage_command, system),
223
+ "python": probe_python(args.python_command),
224
+ }
225
+ print(json.dumps(result, ensure_ascii=False, separators=(",", ":")))
226
+ return 0
227
+
228
+
229
+ if __name__ == "__main__":
230
+ raise SystemExit(main())