Files
last30days-skill/scripts/lib/setup_wizard.py
T
Matt Van Horn 775596ce21 feat: v2.9.6 — free-first NUX, cookie extraction, quality scoring
Setup wizard with consent-first cookie extraction (Chrome/Firefox/Safari),
yt-dlp auto-install, ScrapeCreators push, quality scoring (5 core sources),
status banner redesign, honest Reddit labeling, inline YouTube transcripts,
Exa free web search, Reddit public fallback, and post-research quality nudge.

Co-authored-by: Matt Van Horn <mvanhorn@MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 14:33:17 -07:00

187 lines
6.4 KiB
Python

"""First-run setup wizard for last30days.
Detects first run, performs auto-setup (cookie extraction + yt-dlp check),
and writes configuration. The actual wizard UI is SKILL.md-driven (the LLM
presents it), but this module provides the detection and setup actions.
"""
import logging
import shutil
import subprocess
from pathlib import Path
from typing import Any, Dict, Optional
logger = logging.getLogger(__name__)
def is_first_run(config: Dict[str, Any]) -> bool:
"""Return True if the setup wizard has not been completed.
Checks for SETUP_COMPLETE in the config dict. If it's not set
(None or empty string), the user hasn't gone through setup yet.
"""
return not config.get("SETUP_COMPLETE")
def run_auto_setup(config: Dict[str, Any]) -> Dict[str, Any]:
"""Perform the auto-setup actions.
- Runs cookie extraction in auto mode for all registered domains
- Checks if yt-dlp is installed
Returns:
Dict with keys:
cookies_found: {source_name: browser_name} for each source where cookies were found
ytdlp_installed: bool
env_written: bool (always False here — caller writes config separately)
"""
from . import cookie_extract
from .env import COOKIE_DOMAINS
cookies_found: Dict[str, str] = {}
for source_name, spec in COOKIE_DOMAINS.items():
domain = spec["domain"]
cookie_names = spec["cookies"]
try:
result = cookie_extract.extract_cookies_with_source("auto", domain, cookie_names)
except Exception as exc:
logger.debug("Cookie extraction failed for %s: %s", source_name, exc)
continue
if result is not None:
_cookies, browser_name = result
cookies_found[source_name] = browser_name
# Check yt-dlp availability and install via Homebrew if missing
ytdlp_action: str
if shutil.which("yt-dlp") is not None:
ytdlp_installed = True
ytdlp_action = "already_installed"
elif shutil.which("brew") is not None:
brew_stderr = ""
try:
proc = subprocess.run(
["brew", "install", "yt-dlp"],
capture_output=True, text=True, timeout=120,
)
if proc.returncode == 0:
ytdlp_installed = True
ytdlp_action = "installed"
else:
ytdlp_installed = False
ytdlp_action = "install_failed"
brew_stderr = proc.stderr
logger.warning("brew install yt-dlp failed: %s", proc.stderr)
except Exception as exc:
ytdlp_installed = False
ytdlp_action = "install_failed"
brew_stderr = str(exc)
logger.warning("brew install yt-dlp exception: %s", exc)
else:
ytdlp_installed = False
ytdlp_action = "no_homebrew"
results: Dict[str, Any] = {
"cookies_found": cookies_found,
"ytdlp_installed": ytdlp_installed,
"ytdlp_action": ytdlp_action,
"env_written": False,
}
if ytdlp_action == "install_failed":
results["ytdlp_stderr"] = brew_stderr
return results
def write_setup_config(env_path: Path, from_browser: str = "auto") -> bool:
"""Write SETUP_COMPLETE and FROM_BROWSER to the .env file.
Creates the file and parent directories if needed.
Appends to existing file without overwriting existing keys.
Args:
env_path: Path to the .env file (e.g. ~/.config/last30days/.env)
from_browser: Browser extraction mode to write (default: "auto")
Returns:
True if config was written successfully, False on error.
"""
try:
env_path = Path(env_path)
env_path.parent.mkdir(parents=True, exist_ok=True)
# Read existing content to avoid overwriting keys
existing_keys: set = set()
existing_content = ""
if env_path.exists():
existing_content = env_path.read_text(encoding="utf-8")
for line in existing_content.splitlines():
stripped = line.strip()
if stripped and not stripped.startswith("#") and "=" in stripped:
key = stripped.split("=", 1)[0].strip()
existing_keys.add(key)
lines_to_add = []
if "SETUP_COMPLETE" not in existing_keys:
lines_to_add.append("SETUP_COMPLETE=true")
if "FROM_BROWSER" not in existing_keys:
lines_to_add.append(f"FROM_BROWSER={from_browser}")
if not lines_to_add:
return True # Nothing to write, already configured
# Ensure trailing newline before appending
with open(env_path, "a", encoding="utf-8") as f:
if existing_content and not existing_content.endswith("\n"):
f.write("\n")
f.write("\n".join(lines_to_add) + "\n")
return True
except OSError as exc:
logger.error("Failed to write setup config to %s: %s", env_path, exc)
return False
def get_setup_status_text(results: Dict[str, Any]) -> str:
"""Return a human-readable summary of auto-setup results.
Args:
results: Dict from run_auto_setup()
Returns:
Multi-line status text.
"""
lines = []
lines.append("Setup complete! Here's what I found:")
lines.append("")
cookies_found = results.get("cookies_found", {})
if cookies_found:
for source, browser in cookies_found.items():
lines.append(f" - {source.upper()} cookies found in {browser}")
else:
lines.append(" - No browser cookies found for X/Twitter")
ytdlp_action = results.get("ytdlp_action", "")
if ytdlp_action == "installed":
lines.append(" - Installed yt-dlp via Homebrew")
elif ytdlp_action == "install_failed":
lines.append(" - yt-dlp install failed \u2014 run `brew install yt-dlp` manually")
elif ytdlp_action == "no_homebrew":
lines.append(" - yt-dlp not found. Install Homebrew first, then: brew install yt-dlp")
elif ytdlp_action == "already_installed":
lines.append(" - yt-dlp already installed")
elif results.get("ytdlp_installed", False):
lines.append(" - yt-dlp is installed (YouTube search ready)")
else:
lines.append(" - yt-dlp not found (install with: brew install yt-dlp)")
env_written = results.get("env_written", False)
if env_written:
lines.append("")
lines.append("Configuration saved. Future runs will auto-detect your browsers.")
return "\n".join(lines)