Files
clide/clide-web/clide_web/setup_wizard.py
T
jpmschweitzerandClaude Opus 4.6 56a8ea4ba4 feat: add clide-web package replacing ttyd + zellij
Python web server that wraps Clide for browser access. Replaces the
previous ttyd (C binary) + zellij (Rust binary) stack with a single
FastAPI application using tmux for session persistence.

Key features:
- WebSocket ↔ PTY bridge via tmux attach
- Project switching via /projects/<name> URL routing
- Vendored xterm.js for offline LAN operation
- Auto-respawn on Clide exit (tmux pane-died hook)
- Setup wizard for first-run configuration
- No scrollbar (TUI manages its own scrolling)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:03:43 +01:00

118 lines
3.5 KiB
Python

"""Interactive setup wizard for clide-web configuration."""
from __future__ import annotations
import shutil
from pathlib import Path
from clide.models.db import UserPreference
from clide.services.database import get_engine, init_db
from sqlmodel import Session as DBSession
from sqlmodel import select
def run_setup() -> None:
"""Run the interactive setup wizard, persisting config to the database."""
print()
print(" ╔═══════════════════════════════════════╗")
print(" ║ clide-web Setup Wizard ║")
print(" ╚═══════════════════════════════════════╝")
print()
db_path = Path.home() / ".clide" / "clide.db"
init_db(db_path)
engine = get_engine(db_path)
with DBSession(engine) as db:
# Projects directory
current = _get_pref(db, "projects_dir")
default = current or _guess_projects_dir()
projects_dir = _prompt("Projects directory", default)
path = Path(projects_dir).expanduser().resolve()
if not path.is_dir():
print(f" Warning: '{path}' does not exist yet")
_set_pref(db, "projects_dir", str(path))
# Clide binary
current = _get_pref(db, "clide_bin")
default = current or _find_clide_bin()
clide_bin = _prompt("Clide binary path", default)
_set_pref(db, "clide_bin", clide_bin)
# Port
current = _get_pref(db, "port")
port = _prompt("Server port", current or "8888")
_set_pref(db, "port", port)
# Font size
current = _get_pref(db, "font_size")
font_size = _prompt("Terminal font size", current or "14")
_set_pref(db, "font_size", font_size)
db.commit()
print()
print(" Configuration saved to ~/.clide/clide.db")
print()
print(" Run with: clide-web")
print()
def _prompt(label: str, default: str) -> str:
"""Prompt the user with a default value."""
result = input(f" {label} [{default}]: ").strip()
return result if result else default
def _get_pref(db: DBSession, key: str) -> str | None:
"""Get a preference from the database."""
stmt = select(UserPreference).where(UserPreference.key == key)
pref = db.exec(stmt).first()
return pref.value if pref else None
def _set_pref(db: DBSession, key: str, value: str) -> None:
"""Set a preference in the database."""
stmt = select(UserPreference).where(UserPreference.key == key)
pref = db.exec(stmt).first()
if pref is None:
pref = UserPreference(key=key, value=value)
else:
pref.value = value
db.add(pref)
def _guess_projects_dir() -> str:
"""Try to guess a sensible default for projects directory."""
candidates = [
Path.home() / "Projects",
Path.home() / "projects",
Path.home() / "src",
Path.home() / "code",
Path("/mnt/media/Projects"),
]
for p in candidates:
if p.is_dir():
return str(p)
return str(Path.home() / "Projects")
def _find_clide_bin() -> str:
"""Try to find the clide binary."""
found = shutil.which("clide")
if found:
return found
# Check common venv locations
candidates = [
Path.cwd() / ".venv" / "bin" / "clide",
Path.cwd().parent / ".venv" / "bin" / "clide",
]
for p in candidates:
if p.exists():
return str(p)
return "clide"
if __name__ == "__main__":
run_setup()