PlanOpticon

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

Keyboard Shortcuts

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