PlanOpticon

planopticon / video_processor / cli / init_wizard.py
Source Blame History 200 lines
1707c67… noreply 1 """Interactive setup wizard for PlanOpticon."""
1707c67… noreply 2
1707c67… noreply 3 import os
1707c67… noreply 4 import shutil
1707c67… noreply 5 from pathlib import Path
1707c67… noreply 6 from typing import Dict, Optional, Tuple
1707c67… noreply 7
1707c67… noreply 8 import click
1707c67… noreply 9
1707c67… noreply 10 PROVIDERS = [
1707c67… noreply 11 ("openai", "OpenAI", "OPENAI_API_KEY"),
1707c67… noreply 12 ("anthropic", "Anthropic", "ANTHROPIC_API_KEY"),
1707c67… noreply 13 ("gemini", "Google Gemini", "GEMINI_API_KEY"),
1707c67… noreply 14 ("ollama", "Ollama (local)", None),
1707c67… noreply 15 ("azure", "Azure OpenAI", "AZURE_OPENAI_API_KEY"),
1707c67… noreply 16 ("together", "Together AI", "TOGETHER_API_KEY"),
1707c67… noreply 17 ("fireworks", "Fireworks AI", "FIREWORKS_API_KEY"),
1707c67… noreply 18 ("cerebras", "Cerebras", "CEREBRAS_API_KEY"),
1707c67… noreply 19 ("xai", "xAI", "XAI_API_KEY"),
1707c67… noreply 20 ]
1707c67… noreply 21
1707c67… noreply 22
1707c67… noreply 23 def _check_ffmpeg() -> bool:
1707c67… noreply 24 return shutil.which("ffmpeg") is not None
1707c67… noreply 25
1707c67… noreply 26
1707c67… noreply 27 def _test_provider(provider_id: str, api_key: Optional[str] = None) -> Tuple[bool, str]:
1707c67… noreply 28 """Test that a provider connection works."""
1707c67… noreply 29 if provider_id == "ollama":
1707c67… noreply 30 try:
1707c67… noreply 31 import subprocess
1707c67… noreply 32
1707c67… noreply 33 result = subprocess.run(
1707c67… noreply 34 ["ollama", "list"],
1707c67… noreply 35 capture_output=True,
1707c67… noreply 36 text=True,
1707c67… noreply 37 timeout=5,
1707c67… noreply 38 )
1707c67… noreply 39 if result.returncode == 0:
1707c67… noreply 40 return True, "Ollama is running"
1707c67… noreply 41 return False, "Ollama is installed but not running. Start with: ollama serve"
1707c67… noreply 42 except FileNotFoundError:
1707c67… noreply 43 return False, "Ollama not found. Install from: https://ollama.ai"
1707c67… noreply 44 except Exception as e:
1707c67… noreply 45 return False, f"Could not reach Ollama: {e}"
1707c67… noreply 46
1707c67… noreply 47 if not api_key:
1707c67… noreply 48 return False, "No API key provided"
1707c67… noreply 49
1707c67… noreply 50 # For API-based providers, just check the key looks valid
1707c67… noreply 51 if len(api_key) < 8:
1707c67… noreply 52 return False, "API key looks too short"
1707c67… noreply 53 return True, "API key configured"
1707c67… noreply 54
1707c67… noreply 55
1707c67… noreply 56 def run_wizard() -> None:
1707c67… noreply 57 """Run the interactive setup wizard."""
1707c67… noreply 58 click.echo()
1707c67… noreply 59 click.echo(" PlanOpticon Setup Wizard")
1707c67… noreply 60 click.echo(" " + "-" * 30)
1707c67… noreply 61 click.echo()
1707c67… noreply 62
1707c67… noreply 63 # Step 1: Check prerequisites
1707c67… noreply 64 click.echo("Checking prerequisites...")
1707c67… noreply 65 click.echo()
1707c67… noreply 66
1707c67… noreply 67 if _check_ffmpeg():
1707c67… noreply 68 click.echo(" [ok] FFmpeg found")
1707c67… noreply 69 else:
1707c67… noreply 70 click.echo(" [!!] FFmpeg not found")
1707c67… noreply 71 click.echo(" Install: brew install ffmpeg (macOS)")
1707c67… noreply 72 click.echo(" apt install ffmpeg (Ubuntu)")
1707c67… noreply 73 click.echo(" winget install ffmpeg (Windows)")
1707c67… noreply 74 click.echo()
1707c67… noreply 75
1707c67… noreply 76 # Step 2: Choose provider
1707c67… noreply 77 click.echo()
1707c67… noreply 78 click.echo("Choose your AI provider:")
1707c67… noreply 79 click.echo()
1707c67… noreply 80 for i, (pid, name, _) in enumerate(PROVIDERS, 1):
1707c67… noreply 81 # Check if already configured
1707c67… noreply 82 env_key = PROVIDERS[i - 1][2]
1707c67… noreply 83 status = ""
1707c67… noreply 84 if pid == "ollama":
1707c67… noreply 85 if shutil.which("ollama"):
1707c67… noreply 86 status = " (installed)"
1707c67… noreply 87 elif env_key and os.environ.get(env_key):
1707c67… noreply 88 status = " (configured)"
1707c67… noreply 89 click.echo(f" {i}. {name}{status}")
1707c67… noreply 90 click.echo()
1707c67… noreply 91
1707c67… noreply 92 choice = click.prompt(
1707c67… noreply 93 "Select provider",
1707c67… noreply 94 type=click.IntRange(1, len(PROVIDERS)),
1707c67… noreply 95 default=1,
1707c67… noreply 96 )
1707c67… noreply 97 provider_id, provider_name, env_var = PROVIDERS[choice - 1]
1707c67… noreply 98
1707c67… noreply 99 # Step 3: Configure API key
1707c67… noreply 100 env_vars: Dict[str, str] = {}
1707c67… noreply 101
1707c67… noreply 102 if provider_id == "ollama":
1707c67… noreply 103 click.echo()
1707c67… noreply 104 ok, msg = _test_provider("ollama")
1707c67… noreply 105 if ok:
1707c67… noreply 106 click.echo(f" [ok] {msg}")
1707c67… noreply 107 else:
1707c67… noreply 108 click.echo(f" [!!] {msg}")
1707c67… noreply 109 elif env_var:
1707c67… noreply 110 existing = os.environ.get(env_var, "")
1707c67… noreply 111 if existing:
1707c67… noreply 112 click.echo(f"\n {env_var} is already set.")
1707c67… noreply 113 if not click.confirm(" Update it?", default=False):
1707c67… noreply 114 env_vars[env_var] = existing
1707c67… noreply 115 else:
1707c67… noreply 116 key = click.prompt(f" Enter {env_var}", hide_input=True)
1707c67… noreply 117 env_vars[env_var] = key
1707c67… noreply 118 else:
1707c67… noreply 119 click.echo(f"\n {provider_name} requires {env_var}.")
1707c67… noreply 120 key = click.prompt(f" Enter {env_var}", hide_input=True)
1707c67… noreply 121 env_vars[env_var] = key
1707c67… noreply 122
1707c67… noreply 123 if env_var in env_vars:
1707c67… noreply 124 ok, msg = _test_provider(provider_id, env_vars[env_var])
1707c67… noreply 125 if ok:
1707c67… noreply 126 click.echo(f" [ok] {msg}")
1707c67… noreply 127 else:
1707c67… noreply 128 click.echo(f" [!!] {msg}")
1707c67… noreply 129
1707c67… noreply 130 # Step 4: Additional providers?
1707c67… noreply 131 click.echo()
1707c67… noreply 132 if click.confirm("Configure additional providers?", default=False):
1707c67… noreply 133 for pid, pname, evar in PROVIDERS:
1707c67… noreply 134 if pid == provider_id or not evar:
1707c67… noreply 135 continue
1707c67… noreply 136 if os.environ.get(evar):
1707c67… noreply 137 continue
1707c67… noreply 138 if click.confirm(f" Set up {pname}?", default=False):
1707c67… noreply 139 key = click.prompt(f" Enter {evar}", hide_input=True)
1707c67… noreply 140 env_vars[evar] = key
1707c67… noreply 141
1707c67… noreply 142 # Step 5: Write .env file
1707c67… noreply 143 env_path = Path.cwd() / ".env"
1707c67… noreply 144 if env_vars:
1707c67… noreply 145 click.echo()
1707c67… noreply 146
1707c67… noreply 147 if env_path.exists():
1707c67… noreply 148 click.echo(f" .env already exists at {env_path}")
1707c67… noreply 149 if not click.confirm(" Append new keys?", default=True):
1707c67… noreply 150 click.echo(" Skipping .env update.")
1707c67… noreply 151 _print_summary(provider_name, env_vars)
1707c67… noreply 152 return
1707c67… noreply 153
1707c67… noreply 154 # Read existing content
1707c67… noreply 155 existing_content = env_path.read_text() if env_path.exists() else ""
1707c67… noreply 156 existing_keys = set()
1707c67… noreply 157 for line in existing_content.split("\n"):
1707c67… noreply 158 if "=" in line and not line.strip().startswith("#"):
1707c67… noreply 159 existing_keys.add(line.split("=", 1)[0].strip())
1707c67… noreply 160
1707c67… noreply 161 new_lines = []
1707c67… noreply 162 for key, val in env_vars.items():
1707c67… noreply 163 if key not in existing_keys:
1707c67… noreply 164 new_lines.append(f"{key}={val}")
1707c67… noreply 165
1707c67… noreply 166 if new_lines:
1707c67… noreply 167 with open(env_path, "a") as f:
1707c67… noreply 168 if existing_content and not existing_content.endswith("\n"):
1707c67… noreply 169 f.write("\n")
1707c67… noreply 170 f.write("\n".join(new_lines) + "\n")
1707c67… noreply 171 click.echo(f" Updated {env_path} with {len(new_lines)} key(s)")
1707c67… noreply 172 else:
1707c67… noreply 173 click.echo(" All keys already in .env")
1707c67… noreply 174
1707c67… noreply 175 # Remind about .gitignore
1707c67… noreply 176 gitignore = Path.cwd() / ".gitignore"
1707c67… noreply 177 if gitignore.exists():
1707c67… noreply 178 content = gitignore.read_text()
1707c67… noreply 179 if ".env" not in content:
1707c67… noreply 180 click.echo(" [!!] .env is not in .gitignore — consider adding it")
1707c67… noreply 181 else:
1707c67… noreply 182 click.echo(" [!!] No .gitignore found — make sure .env is not committed")
1707c67… noreply 183
1707c67… noreply 184 _print_summary(provider_name, env_vars)
1707c67… noreply 185
1707c67… noreply 186
1707c67… noreply 187 def _print_summary(provider_name: str, env_vars: Dict[str, str]) -> None:
1707c67… noreply 188 """Print setup summary."""
1707c67… noreply 189 click.echo()
1707c67… noreply 190 click.echo(" Setup complete!")
1707c67… noreply 191 click.echo()
1707c67… noreply 192 click.echo(f" Provider: {provider_name}")
1707c67… noreply 193 if env_vars:
1707c67… noreply 194 click.echo(f" Keys configured: {len(env_vars)}")
1707c67… noreply 195 click.echo()
1707c67… noreply 196 click.echo(" Next steps:")
1707c67… noreply 197 click.echo(" planopticon doctor Check setup health")
1707c67… noreply 198 click.echo(" planopticon analyze -i VIDEO -o OUTPUT")
1707c67… noreply 199 click.echo(" planopticon -I Interactive mode")
1707c67… noreply 200 click.echo()

Keyboard Shortcuts

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