diff --git a/bincio/dev.py b/bincio/dev.py index b40d441..8d8751c 100644 --- a/bincio/dev.py +++ b/bincio/dev.py @@ -68,7 +68,18 @@ def _merge_all_users(data: Path) -> None: _write_root_manifest(data) -def _start_serve(data: Path, api_port: int, site: Path) -> None: +def _local_ip() -> str: + """Return the machine's primary LAN IP (best-effort).""" + import socket + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + return s.getsockname()[0] + except Exception: + return "127.0.0.1" + + +def _start_serve(data: Path, api_port: int, site: Path, api_host: str = "127.0.0.1") -> None: """Start bincio serve in a background thread.""" import uvicorn import bincio.serve.server as srv @@ -78,7 +89,7 @@ def _start_serve(data: Path, api_port: int, site: Path) -> None: config = uvicorn.Config( srv.app, - host="127.0.0.1", + host=api_host, port=api_port, log_level="warning", # quiet — astro dev output takes priority ) @@ -147,11 +158,14 @@ def _watch_data(data: Path) -> None: @click.option("--site-dir", default=None, help="Astro project directory (default: ./site)") @click.option("--port", default=4321, show_default=True, help="Astro dev server port") @click.option("--api-port", default=4041, show_default=True, help="bincio serve API port") +@click.option("--api-host", default="127.0.0.1", show_default=True, + help="Host for bincio serve. Use 0.0.0.0 to expose on the local network for mobile testing.") def dev( data_dir: Optional[str], site_dir: Optional[str], port: int, api_port: int, + api_host: str, ) -> None: """Start the local dev environment: bincio serve + astro dev. @@ -169,11 +183,16 @@ def dev( has_auth = (data / "instance.db").exists() + lan_ip = _local_ip() if api_host == "0.0.0.0" else api_host + mobile_url = f"http://{lan_ip}:{api_port}" if api_host == "0.0.0.0" else None + console.print(f"[bold]bincio dev[/bold]") console.print(f" Data: [cyan]{data}[/cyan]") console.print(f" Site: [cyan]{site}[/cyan]") if has_auth: - console.print(f" API: [cyan]http://127.0.0.1:{api_port}[/cyan]") + console.print(f" API: [cyan]http://{api_host}:{api_port}[/cyan]") + if mobile_url: + console.print(f" Mobile: [bold cyan]{mobile_url}[/bold cyan] ← set this as instance URL in the app") else: console.print(f" Auth: [yellow]none[/yellow] (single-user, no instance.db)") console.print(f" Browser: [cyan]http://localhost:{port}[/cyan]") @@ -196,8 +215,8 @@ def dev( # Start bincio serve only when instance.db exists (auth / write API) if has_auth: - console.print(f"Starting [cyan]bincio serve[/cyan] on port {api_port}…") - t = threading.Thread(target=_start_serve, args=(data, api_port, site), daemon=True) + console.print(f"Starting [cyan]bincio serve[/cyan] on {api_host}:{api_port}…") + t = threading.Thread(target=_start_serve, args=(data, api_port, site, api_host), daemon=True) t.start() # Watch data dir for sidecar/activity changes → auto-merge diff --git a/scripts/dev_test.py b/scripts/dev_test.py index 7d559e9..2ed6f01 100755 --- a/scripts/dev_test.py +++ b/scripts/dev_test.py @@ -11,6 +11,7 @@ Run from the project root: Options: --fresh Wipe DATA_DIR before starting (default: reuse if it exists) --no-dev Stop after extract (skip `bincio dev`) + --mobile Bind API to 0.0.0.0 for mobile app testing on the same WiFi Credentials: dave / testpass and brut / testpass URL: http://localhost:4321 @@ -139,7 +140,7 @@ def prepare_serve() -> None: # ── 4. Hand off to bincio dev ───────────────────────────────────────────────── -def start_dev() -> None: +def start_dev(mobile: bool = False) -> None: section("Starting bincio dev") print() print(" \033[1mCredentials\033[0m") @@ -150,11 +151,12 @@ def start_dev() -> None: print() print(" Press Ctrl+C to stop.\n") + cmd = ["uv", "run", "bincio", "dev", "--data-dir", str(DATA_DIR)] + if mobile: + cmd += ["--api-host", "0.0.0.0"] + try: - subprocess.run( - ["uv", "run", "bincio", "dev", "--data-dir", str(DATA_DIR)], - cwd=PROJECT_DIR, - ) + subprocess.run(cmd, cwd=PROJECT_DIR) except KeyboardInterrupt: pass @@ -166,6 +168,7 @@ def main() -> None: formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--fresh", action="store_true", help="Wipe DATA_DIR before starting") parser.add_argument("--no-dev", action="store_true", help="Stop after extract, skip bincio dev") + parser.add_argument("--mobile", action="store_true", help="Bind API to 0.0.0.0 for local mobile testing") args = parser.parse_args() print(f"\033[1mbincio dev test\033[0m → {DATA_DIR}") @@ -181,7 +184,7 @@ def main() -> None: prepare_serve() if not args.no_dev: - start_dev() + start_dev(mobile=args.mobile) else: print(f"\n\033[32mDone.\033[0m Data ready at {DATA_DIR}") print(f"Run: uv run bincio dev --data-dir {DATA_DIR}\n")