Skills › Scientific Agent Skills › autoskill › doctor.py
doctor.py
126 lines · skills/autoskill/scripts/doctor.py at 5f2bc03
1import argparse
2import os
3import sys
4from pathlib import Path
6import httpx
8from backends import check_remote_endpoint
10_VALID_BACKENDS = {"local", "claude", "foundry"}
13def default_screenpipe_probe(config):
14 sp = config.get("screenpipe", {})
15 try:
16 url = check_remote_endpoint(sp.get("url", "http://localhost:3030"), "screenpipe")
17 except ValueError as exc:
18 return ("error", str(exc))
19 token = sp.get("token") or os.environ.get("SCREENPIPE_TOKEN")
20 headers = {"Authorization": f"Bearer {token}"} if token else {}
21 try:
22 r = httpx.get(f"{url.rstrip('/')}/health", headers=headers, timeout=5.0)
23 if r.status_code == 200:
24 return ("ok", f"{url} reachable; /health is public, search auth not verified")
25 return ("error", f"{url} returned HTTP {r.status_code}")
26 except httpx.HTTPError as e:
27 return ("error", f"{url}: {e}")
30def default_llm_probe(config):
31 kind = config.get("backend")
32 if kind == "local":
33 endpoint = config.get("local", {}).get("endpoint", "http://localhost:1234/v1")
34 try:
35 endpoint = check_remote_endpoint(endpoint, "local")
36 token = os.environ.get("LM_API_TOKEN")
37 headers = {"Authorization": f"Bearer {token}"} if token else {}
38 r = httpx.get(f"{endpoint.rstrip('/')}/models", headers=headers, timeout=5.0)
39 if r.status_code == 200:
40 model = config.get("local", {}).get("model")
41 models = r.json().get("data", [])
42 if not model or model not in {m.get("id") for m in models}:
43 return ("error", "local.model is not listed by /models; load it or use its exact ID")
44 return ("ok", f"{endpoint}: model visible; inference not probed")
45 return ("error", f"{endpoint} returned HTTP {r.status_code}")
46 except (ValueError, TypeError, AttributeError) as e:
47 return ("error", f"invalid local endpoint or models response: {e}")
48 except httpx.HTTPError as e:
49 return ("error", f"{endpoint}: {e}")
50 if kind == "claude":
51 if not os.environ.get("ANTHROPIC_API_KEY"):
52 return ("error", "ANTHROPIC_API_KEY not set")
53 return ("ok", "ANTHROPIC_API_KEY present (not probed)")
54 if kind == "foundry":
55 try:
56 check_remote_endpoint(config.get("foundry", {}).get("endpoint", ""), "foundry")
57 except ValueError as e:
58 return ("error", str(e))
59 if not os.environ.get("FOUNDRY_API_KEY"):
60 return ("error", "FOUNDRY_API_KEY not set")
61 return ("ok", "FOUNDRY_API_KEY present (not probed)")
62 return ("error", f"unknown backend: {kind!r}")
65def check(config, *, skills_dir, screenpipe_probe, llm_probe):
66 result = {}
68 kind = config.get("backend")
69 if kind in _VALID_BACKENDS:
70 result["config"] = ("ok", f"backend={kind}")
71 else:
72 result["config"] = ("error", f"unknown backend: {kind!r}")
74 skills_dir = Path(skills_dir)
75 if skills_dir.is_dir():
76 result["skills_dir"] = ("ok", str(skills_dir))
77 else:
78 result["skills_dir"] = ("error", f"not a directory: {skills_dir}")
80 result["screenpipe"] = screenpipe_probe(config)
81 result["llm"] = llm_probe(config)
82 return result
85def _format_report(result: dict) -> str:
86 lines = ["autoskill doctor", "================"]
87 for key in ("config", "skills_dir", "screenpipe", "llm"):
88 status, detail = result[key]
89 lines.append(f" {key:12s}: {status:5s} {detail}")
90 return "\n".join(lines)
93def main(argv=None):
94 import yaml
96 parser = argparse.ArgumentParser(
97 prog="autoskill-doctor",
98 description="Check that screenpipe, LM Studio, config, and skills dir are ready.",
99 )
100 parser.add_argument("--config", required=True,
101 help="path to autoskill config.yaml")
102 parser.add_argument("--skills-dir", required=True,
103 help="path to skills/")
104 args = parser.parse_args(argv)
106 config = yaml.safe_load(Path(args.config).read_text())
107 result = check(
108 config,
109 skills_dir=args.skills_dir,
110 screenpipe_probe=default_screenpipe_probe,
111 llm_probe=default_llm_probe,
112 )
114 report = _format_report(result)
115 print(report)
117 any_error = any(status == "error" for status, _ in result.values())
118 if any_error:
119 print("\ndoctor: one or more checks failed", file=sys.stderr)
120 return 1
121 print("\ndoctor: all checks passed")
122 return 0
125if __name__ == "__main__":
126 raise SystemExit(main())