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