support localhost installation/caching

Hunter
Monday, November 10th, 2025 at 11:59 PM

generate_manifests.py+69-44
host.py+30-1

generate_manifests.py
  8  import re
  9  from pathlib import Path
 10  
    -def get_configuration():
    -	"""Prompt user for configuration values"""
 11 +def get_configuration(localhost=False):
 12 +	"""Prompt user for configuration values
 13 +
 14 +	Args:
 15 +		localhost: If True, assumes root path and only asks for app name.
 16 +		           This enables PWA installation on localhost (iOS 18+).
 17 +	"""
 18  	print("=" * 60)
 19  	print("PWA Configuration")
 20  	print("=" * 60)
 21  	print()
 22  
 23  	# Get app name
    -	app_name = input("Enter a name for your mixapp: ").strip()
    -	if not app_name:
    -		print("Error: App name is required")
    -		exit(1)
    -
    -	# Get base path with smart default
    -	default_path = app_name.lower().replace(" ", "_")
    -	print()
    -	print(f"Enter the deployment path (or press Return/Enter for default)")
    -	print(f"Default: /{default_path}/")
    -	base_path_input = input("Path: ").strip()
    -
    -	if base_path_input:
    -		# User provided a path - ensure it has leading/trailing slashes
    -		base_path = base_path_input
    -		if not base_path.startswith("/"):
    -			base_path = "/" + base_path
    -		if not base_path.endswith("/"):
    -			base_path = base_path + "/"
 24 +	if localhost:
 25 +		app_name = input("Enter a name for your mixapp (or press Return/Enter for 'vibe capsule'): ").strip()
 26 +		if not app_name:
 27 +			app_name = "vibe capsule"
 28 +			print(f"Using default: {app_name}")
 29  	else:
    -		# Use default
    -		base_path = f"/{default_path}/"
    -		print(f"Using default path: {base_path}")
 30 +		app_name = input("Enter a name for your mixapp: ").strip()
 31 +		if not app_name:
 32 +			print("Error: App name is required")
 33 +			exit(1)
 34 +
 35 +	# For localhost mode, use root path
 36 +	if localhost:
 37 +		base_path = "/"
 38 +		print()
 39 +		print("Localhost mode: Using root path for PWA installation")
 40 +	else:
 41 +		# Get base path with smart default
 42 +		default_path = app_name.lower().replace(" ", "_")
 43 +		print()
 44 +		print(f"Enter the deployment path (or press Return/Enter for default)")
 45 +		print(f"Default: /{default_path}/")
 46 +		base_path_input = input("Path: ").strip()
 47 +
 48 +		if base_path_input:
 49 +			# User provided a path - ensure it has leading/trailing slashes
 50 +			base_path = base_path_input
 51 +			if not base_path.startswith("/"):
 52 +				base_path = "/" + base_path
 53 +			if not base_path.endswith("/"):
 54 +				base_path = base_path + "/"
 55 +		else:
 56 +			# Use default
 57 +			base_path = f"/{default_path}/"
 58 +			print(f"Using default path: {base_path}")
 59  
 60  	print()
 61  	print(f"Configuration:")

 65  
 66  	return app_name, base_path
 67  
    -# Get configuration from user
    -APP_NAME, BASE_PATH = get_configuration()
    -
    -# Derived values (can be manually overridden if desired)
    -SHORT_NAME = APP_NAME
    -CACHE_NAME = APP_NAME
    -APP_DESCRIPTION = f"{APP_NAME} · vibe capsule"
    -
 68  # File paths (no need to edit these)
 69  SCRIPT_DIR = Path(__file__).parent.absolute()
 70  TRACKS_JSON = SCRIPT_DIR / "tracks" / "tracks.json"

 94  	return "#080a0c"
 95  
 96  
    -def generate_pwa_manifests():
    -	"""Generate PWA manifest files based on tracks.json"""
 97 +def generate_pwa_manifests(app_name=None, base_path=None):
 98 +	"""Generate PWA manifest files based on tracks.json
 99 +
100 +	Args:
101 +		app_name: Name of the app. If None, will be prompted via get_configuration()
102 +		base_path: Base path for the app. If None, will be prompted via get_configuration()
103 +	"""
104 +	# Get configuration if not provided
105 +	if app_name is None or base_path is None:
106 +		app_name, base_path = get_configuration()
107 +
108 +	# Derived values
109 +	short_name = app_name
110 +	cache_name = app_name
111 +	app_description = f"{app_name} · vibe capsule"
112 +
113  	print("Generating PWA manifests...")
114  
115  	# Load tracks.json

125  
126  	# Generate manifest.json
127  	manifest = {
    -		"id": BASE_PATH,
    -		"name": APP_NAME,
    -		"short_name": SHORT_NAME,
    -		"description": APP_DESCRIPTION,
    -		"start_url": BASE_PATH,
    -		"scope": BASE_PATH,
128 +		"id": base_path,
129 +		"name": app_name,
130 +		"short_name": short_name,
131 +		"description": app_description,
132 +		"start_url": base_path,
133 +		"scope": base_path,
134  		"display": "standalone",
135  		"background_color": background_color,
136  		"theme_color": background_color,
    -		"cache_name": CACHE_NAME,  # Custom field for script.js to use
137 +		"cache_name": cache_name,  # Custom field for script.js to use
138  		"icons": [
139  			{
140  				"src": "resources/icon.png",

173  
174  	# Generate service-worker.js
175  	static_files = resource_manifest["static_files"]
    -	service_worker_content = f'''// Auto-generated service worker for {APP_NAME} PWA
    -const CACHE_NAME = '{CACHE_NAME}';
176 +	service_worker_content = f'''// Auto-generated service worker for {app_name} PWA
177 +const CACHE_NAME = '{cache_name}';
178  const staticFilesToCache = {json.dumps(static_files, indent=2)};
179  
180  // Get the base path from the service worker location

306  
307  
308  if __name__ == "__main__":
    -	generate_pwa_manifests()
309 +	# When run directly, get configuration and generate manifests
310 +	app_name, base_path = get_configuration()
311 +	generate_pwa_manifests(app_name, base_path)
host.py
116  		print(f"\nCould not generate QR code: {e}")
117  
118  
119 +def generate_localhost_manifests():
120 +	"""Generate PWA manifests for localhost usage"""
121 +	try:
122 +		# Import the generate_manifests module
123 +		sys.path.insert(0, str(SCRIPT_DIR))
124 +		from generate_manifests import get_configuration, generate_pwa_manifests
125 +
126 +		# Get configuration with localhost mode enabled
127 +		print()
128 +		app_name, base_path = get_configuration(localhost=True)
129 +
130 +		# Generate the manifests
131 +		generate_pwa_manifests(app_name, base_path)
132 +		print()
133 +
134 +		return app_name
135 +
136 +	except ImportError as e:
137 +		print(f"Error: Could not import generate_manifests.py: {e}")
138 +		print("Make sure generate_manifests.py is in the same directory.")
139 +		sys.exit(1)
140 +	except Exception as e:
141 +		print(f"Error generating manifests: {e}")
142 +		sys.exit(1)
143 +
144 +
145  def start_server():
146  	"""Start the HTTP server (runs after venv is set up)"""
147  	# Change to script directory
148  	os.chdir(SCRIPT_DIR)
149  
150 +	# Generate manifests for localhost
151 +	app_name = generate_localhost_manifests()
152 +
153  	# Find an available port
154  	port = find_available_port(DEFAULT_PORT)
155  

182  			network_url = f"http://{local_ip}:{port}"
183  
184  			print("=" * 60)
    -			print("💿 vibe capsule")
185 +			print(f"💿 {app_name}")
186  			print("=" * 60)
187  			print(f"\nServer running on port {port}")
188