PlanOpticon

planopticon / video_processor / cli / doctor.py
Source Blame History 194 lines
1707c67… noreply 1 """Diagnostic checks for PlanOpticon setup."""
1707c67… noreply 2
1707c67… noreply 3 import logging
1707c67… noreply 4 import os
1707c67… noreply 5 import shutil
1707c67… noreply 6 import sys
1707c67… noreply 7 from pathlib import Path
1707c67… noreply 8 from typing import List, Tuple
1707c67… noreply 9
1707c67… noreply 10 logger = logging.getLogger(__name__)
1707c67… noreply 11
1707c67… noreply 12 # (check_name, status, detail)
1707c67… noreply 13 CheckResult = Tuple[str, str, str]
1707c67… noreply 14
1707c67… noreply 15
1707c67… noreply 16 def check_python_version() -> CheckResult:
1707c67… noreply 17 """Check Python version meets minimum."""
1707c67… noreply 18 v = sys.version_info
1707c67… noreply 19 version = f"{v.major}.{v.minor}.{v.micro}"
1707c67… noreply 20 if v >= (3, 10):
1707c67… noreply 21 return ("Python", "ok", version)
1707c67… noreply 22 return ("Python", "warn", f"{version} (3.10+ recommended)")
1707c67… noreply 23
1707c67… noreply 24
1707c67… noreply 25 def check_ffmpeg() -> CheckResult:
1707c67… noreply 26 """Check if ffmpeg is installed and accessible."""
1707c67… noreply 27 path = shutil.which("ffmpeg")
1707c67… noreply 28 if path:
1707c67… noreply 29 return ("FFmpeg", "ok", path)
1707c67… noreply 30 return ("FFmpeg", "missing", "Install via: brew install ffmpeg / apt install ffmpeg")
1707c67… noreply 31
1707c67… noreply 32
1707c67… noreply 33 def check_api_keys() -> List[CheckResult]:
1707c67… noreply 34 """Check for configured API keys."""
1707c67… noreply 35 keys = {
1707c67… noreply 36 "OpenAI": "OPENAI_API_KEY",
1707c67… noreply 37 "Anthropic": "ANTHROPIC_API_KEY",
1707c67… noreply 38 "Google Gemini": "GEMINI_API_KEY",
1707c67… noreply 39 "Azure OpenAI": "AZURE_OPENAI_API_KEY",
1707c67… noreply 40 "Together": "TOGETHER_API_KEY",
1707c67… noreply 41 "Fireworks": "FIREWORKS_API_KEY",
1707c67… noreply 42 "Cerebras": "CEREBRAS_API_KEY",
1707c67… noreply 43 "xAI": "XAI_API_KEY",
1707c67… noreply 44 "Mistral": "MISTRAL_API_KEY",
1707c67… noreply 45 "Cohere": "COHERE_API_KEY",
1707c67… noreply 46 "HuggingFace": "HUGGINGFACE_API_KEY",
1707c67… noreply 47 }
1707c67… noreply 48 results = []
1707c67… noreply 49 for name, env in keys.items():
1707c67… noreply 50 val = os.environ.get(env, "")
1707c67… noreply 51 if val:
1707c67… noreply 52 masked = val[:4] + "..." + val[-4:] if len(val) > 8 else "***"
1707c67… noreply 53 results.append((f" {name}", "ok", f"{env}={masked}"))
1707c67… noreply 54 else:
1707c67… noreply 55 results.append((f" {name}", "not set", env))
1707c67… noreply 56 return results
1707c67… noreply 57
1707c67… noreply 58
1707c67… noreply 59 def check_ollama() -> CheckResult:
1707c67… noreply 60 """Check if Ollama is running locally."""
1707c67… noreply 61 path = shutil.which("ollama")
1707c67… noreply 62 if not path:
1707c67… noreply 63 return ("Ollama", "not installed", "Optional: https://ollama.ai")
1707c67… noreply 64 try:
1707c67… noreply 65 import subprocess
1707c67… noreply 66
1707c67… noreply 67 result = subprocess.run(
1707c67… noreply 68 ["ollama", "list"],
1707c67… noreply 69 capture_output=True,
1707c67… noreply 70 text=True,
1707c67… noreply 71 timeout=5,
1707c67… noreply 72 )
1707c67… noreply 73 if result.returncode == 0:
1707c67… noreply 74 models = [
1707c67… noreply 75 line.split()[0] for line in result.stdout.strip().split("\n")[1:] if line.strip()
1707c67… noreply 76 ]
1707c67… noreply 77 if models:
1707c67… noreply 78 return ("Ollama", "ok", f"{len(models)} models: {', '.join(models[:3])}")
1707c67… noreply 79 return ("Ollama", "ok", "Running but no models pulled")
1707c67… noreply 80 return ("Ollama", "warn", "Installed but not running")
1707c67… noreply 81 except Exception:
1707c67… noreply 82 return ("Ollama", "warn", "Installed but not reachable")
1707c67… noreply 83
1707c67… noreply 84
1707c67… noreply 85 def check_optional_deps() -> List[CheckResult]:
1707c67… noreply 86 """Check optional Python dependencies."""
1707c67… noreply 87 deps = [
1707c67… noreply 88 ("reportlab", "PDF export"),
1707c67… noreply 89 ("pptx", "PPTX export"),
1707c67… noreply 90 ("markdown", "HTML reports"),
1707c67… noreply 91 ("torch", "GPU acceleration"),
1707c67… noreply 92 ("yt_dlp", "YouTube download"),
1707c67… noreply 93 ("feedparser", "RSS sources"),
1707c67… noreply 94 ("bs4", "Web scraping"),
1707c67… noreply 95 ]
1707c67… noreply 96 results = []
1707c67… noreply 97 for module, purpose in deps:
1707c67… noreply 98 try:
1707c67… noreply 99 __import__(module)
1707c67… noreply 100 results.append((f" {module}", "ok", purpose))
1707c67… noreply 101 except ImportError:
1707c67… noreply 102 results.append((f" {module}", "not installed", purpose))
1707c67… noreply 103 return results
1707c67… noreply 104
1707c67… noreply 105
1707c67… noreply 106 def check_dotenv() -> CheckResult:
1707c67… noreply 107 """Check if .env file exists in current directory."""
1707c67… noreply 108 env_path = Path.cwd() / ".env"
1707c67… noreply 109 if env_path.exists():
1707c67… noreply 110 return (".env file", "ok", str(env_path))
1707c67… noreply 111 return (".env file", "not found", "Run `planopticon init` to create one")
1707c67… noreply 112
1707c67… noreply 113
1707c67… noreply 114 def check_knowledge_graph() -> CheckResult:
1707c67… noreply 115 """Check for knowledge graph files in common locations."""
1707c67… noreply 116 from video_processor.integrators.graph_discovery import find_nearest_graph
1707c67… noreply 117
1707c67… noreply 118 path = find_nearest_graph()
1707c67… noreply 119 if path:
1707c67… noreply 120 return ("Knowledge graph", "ok", str(path))
1707c67… noreply 121 return ("Knowledge graph", "not found", "Run `planopticon analyze` to create one")
1707c67… noreply 122
1707c67… noreply 123
1707c67… noreply 124 def run_all_checks() -> List[CheckResult]:
1707c67… noreply 125 """Run all diagnostic checks and return results."""
1707c67… noreply 126 results = []
1707c67… noreply 127
1707c67… noreply 128 results.append(check_python_version())
1707c67… noreply 129 results.append(check_ffmpeg())
1707c67… noreply 130 results.append(check_dotenv())
1707c67… noreply 131
1707c67… noreply 132 results.append(("API Keys", "section", ""))
1707c67… noreply 133 results.extend(check_api_keys())
1707c67… noreply 134
1707c67… noreply 135 results.append(check_ollama())
1707c67… noreply 136
1707c67… noreply 137 results.append(("Optional Dependencies", "section", ""))
1707c67… noreply 138 results.extend(check_optional_deps())
1707c67… noreply 139
1707c67… noreply 140 results.append(check_knowledge_graph())
1707c67… noreply 141
1707c67… noreply 142 return results
1707c67… noreply 143
1707c67… noreply 144
1707c67… noreply 145 def format_results(results: List[CheckResult]) -> str:
1707c67… noreply 146 """Format check results for terminal display."""
1707c67… noreply 147 lines = ["", "PlanOpticon Doctor", ""]
1707c67… noreply 148 status_icons = {
1707c67… noreply 149 "ok": "[ok]",
1707c67… noreply 150 "warn": "[!!]",
1707c67… noreply 151 "missing": "[XX]",
1707c67… noreply 152 "not set": "[--]",
1707c67… noreply 153 "not found": "[--]",
1707c67… noreply 154 "not installed": "[--]",
1707c67… noreply 155 "section": "---",
1707c67… noreply 156 }
1707c67… noreply 157
1707c67… noreply 158 any_issues = False
1707c67… noreply 159 for name, status, detail in results:
1707c67… noreply 160 icon = status_icons.get(status, "[??]")
1707c67… noreply 161 if status == "section":
1707c67… noreply 162 lines.append(f"\n{name}:")
1707c67… noreply 163 continue
1707c67… noreply 164 if status in ("missing", "warn"):
1707c67… noreply 165 any_issues = True
1707c67… noreply 166 detail_str = f" {detail}" if detail else ""
1707c67… noreply 167 lines.append(f" {icon} {name}{detail_str}")
1707c67… noreply 168
1707c67… noreply 169 lines.append("")
1707c67… noreply 170 if any_issues:
1707c67… noreply 171 lines.append("Some issues found. Run `planopticon init` for guided setup.")
1707c67… noreply 172 else:
1707c67… noreply 173 has_key = any(
1707c67… noreply 174 s == "ok"
1707c67… noreply 175 for n, s, _ in results
1707c67… noreply 176 if n.strip()
1707c67… noreply 177 in (
1707c67… noreply 178 "OpenAI",
1707c67… noreply 179 "Anthropic",
1707c67… noreply 180 "Google Gemini",
1707c67… noreply 181 "Azure OpenAI",
1707c67… noreply 182 "Together",
1707c67… noreply 183 "Fireworks",
1707c67… noreply 184 "Cerebras",
1707c67… noreply 185 "xAI",
1707c67… noreply 186 )
1707c67… noreply 187 )
1707c67… noreply 188 if has_key:
1707c67… noreply 189 lines.append("Setup looks good!")
1707c67… noreply 190 else:
1707c67… noreply 191 lines.append("No API keys configured. Run `planopticon init` to set up a provider.")
1707c67… noreply 192 lines.append("")
1707c67… noreply 193
1707c67… noreply 194 return "\n".join(lines)

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button