Spaces:
Sleeping
Sleeping
File size: 7,914 Bytes
f103ad7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | """
Subprocess wrapper for the biomcp CLI.
Runs biomcp commands and returns structured (JSON) or markdown output.
"""
import json
import os
import subprocess
import shutil
import sys
from typing import Optional
def find_biomcp() -> str:
"""Locate the biomcp binary on PATH or common install locations."""
path = shutil.which("biomcp")
if path:
return path
# Check Python Scripts directory (pip install location)
candidates = [
os.path.join(os.path.dirname(sys.executable), "Scripts", "biomcp.exe"),
os.path.join(os.path.dirname(sys.executable), "Scripts", "biomcp"),
os.path.join(os.path.dirname(sys.executable), "biomcp.exe"),
os.path.join(os.path.dirname(sys.executable), "biomcp"),
]
# Also check the Python that `py` launcher uses
for base in [
os.path.expanduser("~/.local/bin"),
os.path.expandvars(r"%LOCALAPPDATA%\Python\pythoncore-3.14-64\Scripts"),
os.path.expandvars(r"%LOCALAPPDATA%\Python\pythoncore-3.13-64\Scripts"),
os.path.expandvars(r"%LOCALAPPDATA%\Python\pythoncore-3.12-64\Scripts"),
]:
candidates.append(os.path.join(base, "biomcp.exe"))
candidates.append(os.path.join(base, "biomcp"))
for c in candidates:
if os.path.isfile(c):
return c
raise FileNotFoundError(
"biomcp not found on PATH. Install with: pip install biomcp-cli"
)
def run(
args: list[str],
*,
json_mode: bool = True,
no_cache: bool = False,
env_overrides: Optional[dict[str, str]] = None,
timeout: int = 120,
) -> dict:
"""
Execute a biomcp CLI command.
Returns dict with keys:
- success: bool
- command: str (the full command string)
- markdown: str (raw stdout, always present)
- data: dict | list | None (parsed JSON when json_mode=True)
- error: str | None
"""
import os
biomcp = find_biomcp()
cmd = [biomcp]
if json_mode:
cmd.append("--json")
if no_cache:
cmd.append("--no-cache")
cmd.extend(args)
env = os.environ.copy()
if env_overrides:
for k, v in env_overrides.items():
if v: # only set non-empty values
env[k] = v
command_str = " ".join(cmd)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
env=env,
)
stdout = result.stdout.strip()
stderr = result.stderr.strip()
if result.returncode != 0:
error_msg = stderr or stdout or f"Command exited with code {result.returncode}"
return {
"success": False,
"command": command_str,
"markdown": error_msg,
"data": None,
"error": error_msg,
}
# Try parsing JSON
data = None
if json_mode and stdout:
try:
data = json.loads(stdout)
except json.JSONDecodeError:
pass
# For markdown display: if we got JSON, pretty-print it; otherwise use raw stdout
if data is not None:
markdown = _json_to_markdown(data)
else:
markdown = stdout
return {
"success": True,
"command": command_str,
"markdown": markdown,
"data": data,
"error": None,
}
except subprocess.TimeoutExpired:
return {
"success": False,
"command": command_str,
"markdown": "",
"data": None,
"error": f"Command timed out after {timeout}s",
}
except FileNotFoundError:
return {
"success": False,
"command": command_str,
"markdown": "",
"data": None,
"error": "biomcp binary not found. Install with: uv tool install biomcp-cli",
}
except Exception as e:
return {
"success": False,
"command": command_str,
"markdown": "",
"data": None,
"error": str(e),
}
def run_markdown(
args: list[str],
*,
no_cache: bool = False,
env_overrides: Optional[dict[str, str]] = None,
timeout: int = 120,
) -> dict:
"""Run command in markdown mode (no --json flag)."""
return run(
args,
json_mode=False,
no_cache=no_cache,
env_overrides=env_overrides,
timeout=timeout,
)
def _is_small_dict(d: dict) -> bool:
"""Check if a dict is simple enough to render inline."""
return (len(d) <= 4
and all(isinstance(v, (str, int, float, bool, type(None))) for v in d.values()))
def _json_to_markdown(data, level=1) -> str:
"""Convert structured JSON response to readable markdown."""
if isinstance(data, str):
return data
if isinstance(data, (int, float, bool)):
return str(data)
if data is None:
return ""
if isinstance(data, list):
if not data:
return ""
# List of simple values
if all(isinstance(v, (str, int, float, bool)) for v in data):
return ", ".join(str(v) for v in data)
# List of small dicts — render as compact table-like rows
if all(isinstance(v, dict) and _is_small_dict(v) for v in data):
rows = []
for item in data[:15]:
parts = [f"{v}" for v in item.values() if v is not None and v != ""]
rows.append(" · ".join(parts))
result = "\n".join(f"- {r}" for r in rows)
if len(data) > 15:
result += f"\n- *(+{len(data) - 15} more)*"
return result
# List of larger dicts — render each as a block
parts = []
for i, item in enumerate(data, 1):
if isinstance(item, dict):
label = (item.get("label") or item.get("name") or item.get("title")
or item.get("primary_id") or item.get("id") or f"Item {i}")
hdr = "#" * min(level + 1, 5)
parts.append(f"{hdr} {i}. {label}")
parts.append(_format_dict(item, level + 1))
else:
parts.append(f"- {item}")
return "\n\n".join(parts)
if isinstance(data, dict):
return _format_dict(data, level)
return str(data)
def _format_dict(d: dict, level: int = 1) -> str:
"""Format a dict as readable markdown key-value pairs."""
lines = []
skip = {"_meta", "_links"}
for key, value in d.items():
if key in skip or key.startswith("_"):
continue
nice_key = key.replace("_", " ").replace("-", " ").title()
if value is None or value == "" or value == []:
continue
if isinstance(value, (str, int, float, bool)):
lines.append(f"**{nice_key}:** {value}")
elif isinstance(value, list):
if all(isinstance(v, (str, int, float)) for v in value):
shown = ", ".join(str(v) for v in value[:8])
more = f" *(+{len(value) - 8} more)*" if len(value) > 8 else ""
lines.append(f"**{nice_key}:** {shown}{more}")
elif all(isinstance(v, dict) and _is_small_dict(v) for v in value):
hdr = "#" * min(level + 1, 5)
lines.append(f"\n{hdr} {nice_key}\n")
lines.append(_json_to_markdown(value, level + 1))
else:
hdr = "#" * min(level + 1, 5)
lines.append(f"\n{hdr} {nice_key}\n")
lines.append(_json_to_markdown(value, level + 1))
elif isinstance(value, dict):
hdr = "#" * min(level + 1, 5)
lines.append(f"\n{hdr} {nice_key}\n")
lines.append(_format_dict(value, level + 1))
return "\n\n".join(lines)
|