#!/usr/bin/env python3 """ spr init — CLI room scaffolding wizard Interactive terminal wizard that walks through layers 0-6, generating cfg// for a new soleprint room. Usage: python -m init.cli myroom # Interactive wizard python -m init.cli myroom --from sample # Clone existing room as variant """ import argparse import logging import sys from init.core import ( CFG_DIR, BACKEND_FRAMEWORKS, FRONTEND_FRAMEWORKS, clone_room, generate_layer0, generate_layer1, generate_layer2, generate_layer3, generate_layer4, generate_layer5, generate_layer6, next_free_port, ) log = logging.getLogger("init") # --------------------------------------------------------------------------- # Terminal prompts # --------------------------------------------------------------------------- def ask(prompt: str, default: str = "") -> str: try: if default: val = input(f"{prompt} [{default}]: ").strip() return val or default return input(f"{prompt}: ").strip() except EOFError: return default or "" def ask_yn(prompt: str, default: bool = True) -> bool: suffix = "[Y/n]" if default else "[y/N]" try: val = input(f"{prompt} {suffix}: ").strip().lower() except EOFError: return default if not val: return default return val in ("y", "yes") def ask_choice(prompt: str, choices: list[str], default: str = "") -> str: choices_str = "/".join(choices) try: if default: val = input(f"{prompt} [{choices_str}] ({default}): ").strip().lower() return val if val in choices else default while True: val = input(f"{prompt} [{choices_str}]: ").strip().lower() if val in choices: return val log.warning(" Choose one of: %s", choices_str) except EOFError: return default or choices[0] # --------------------------------------------------------------------------- # Wizard # --------------------------------------------------------------------------- def wizard(room: str): room_dir = CFG_DIR / room if room_dir.exists(): log.error("'%s' already exists in cfg/", room) sys.exit(1) port = next_free_port() log.info("\n=== Soleprint Room Setup: %s ===", room) log.info(" Target: cfg/%s/", room) room_type = ask_choice("Room type?", ["standalone", "managed"], "standalone") port = int(ask("Hub port", str(port))) is_managed = room_type == "managed" # Layer 0: always config = generate_layer0(room_dir, room, port, is_managed) # Layer 1: docker if ask_yn("\nContinue to Layer 1 (Docker)?"): generate_layer1(room_dir, room, port) else: log.info("Done! Build with: python build.py --cfg %s", room) return app_info = None # Layer 2: managed app if is_managed: if ask_yn("\nContinue to Layer 2 (Managed App)?"): app_name = ask("App name", room) backend_path = ask("Backend repo path") backend_framework = ask_choice("Backend framework?", BACKEND_FRAMEWORKS, "django") frontend_path = ask("Frontend repo path [skip]") has_frontend = bool(frontend_path) and frontend_path.lower() != "skip" frontend_framework = None if has_frontend: frontend_framework = ask_choice("Frontend framework?", FRONTEND_FRAMEWORKS, "nextjs") app_info = generate_layer2( room_dir, room, config, app_name, backend_path, backend_framework, frontend_path if has_frontend else None, frontend_framework, ) else: log.info("Done! Build with: python build.py --cfg %s", room) return # Layer 3: link if ask_yn("\nContinue to Layer 3 (Link - DB Bridge)?", default=False): generate_layer3(room_dir, room, app_info["backend_framework"]) # Layer 4: scripts app_name = app_info["app_name"] if app_info else None if ask_yn("\nContinue to Layer 4 (Scripts)?"): generate_layer4(room_dir, room, app_name) # Layer 5: tester if ask_yn("\nContinue to Layer 5 (Test Suite)?", default=False): url = ask("Test target URL", "http://localhost:8000") generate_layer5(room_dir, room, url) # Layer 6: nginx (only if managed with frontend) if is_managed and app_info and app_info["has_frontend"]: if ask_yn("\nContinue to Layer 6 (Nginx Sidebar Injection)?", default=False): generate_layer6(room_dir, room, app_info["app_name"]) log.info("Ready! Build with: python build.py --cfg %s", room) def main(): logging.basicConfig(level=logging.INFO, format="%(message)s") parser = argparse.ArgumentParser( description="Soleprint room scaffolding wizard", usage="python -m init.cli [--from ]", ) parser.add_argument("room", help="Name of the new room") parser.add_argument("--from", dest="from_room", help="Clone from existing room") args = parser.parse_args() if args.from_room: clone_room(args.from_room, args.room) else: wizard(args.room) if __name__ == "__main__": main()