app.py
python
sha256:8d7f41aafae41deee70035a92f93602ef1722a290153597451e5932af503a42c
docs: queue board-identity follow-ups so they survive the session
Human
59 minutes ago
| 1 | """``overseer app`` command (§Q0.4).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import sys |
| 6 | import webbrowser |
| 7 | from argparse import Namespace |
| 8 | from pathlib import Path |
| 9 | |
| 10 | from cli.context import CliContext |
| 11 | from cli.paths import PathEscapeError, resolve_repo_root |
| 12 | from tools.app.bind import DEFAULT_PORT, port_is_available, validate_bind_address |
| 13 | from tools.app.server import run_app_server |
| 14 | |
| 15 | |
| 16 | def _print_startup_banner(config_bind: str, port: int, session: str, csrf: str) -> None: |
| 17 | host = "127.0.0.1" if config_bind in {"127.0.0.1", "localhost"} else "[::1]" |
| 18 | url = f"http://{host}:{port}/" |
| 19 | print("ok app listening", file=sys.stderr) |
| 20 | print(f"url: {url}", file=sys.stderr) |
| 21 | print(f"session_credential: {session}", file=sys.stderr) |
| 22 | print(f"csrf_token: {csrf}", file=sys.stderr) |
| 23 | |
| 24 | |
| 25 | def run_app(args: Namespace, ctx: CliContext) -> int: |
| 26 | """Start the local loopback web UI.""" |
| 27 | try: |
| 28 | repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="app") |
| 29 | except PathEscapeError: |
| 30 | ctx.output.error("refused: repo path outside allowed root") |
| 31 | return 4 |
| 32 | |
| 33 | if not (repo_root / ".overseer").is_dir(): |
| 34 | ctx.output.error("not initialized — run ok init first") |
| 35 | return 2 |
| 36 | |
| 37 | bind = validate_bind_address(args.bind) |
| 38 | if bind is None: |
| 39 | ctx.output.error("refused: bind address must be loopback (127.0.0.1, localhost, or ::1)") |
| 40 | return 2 |
| 41 | |
| 42 | port = int(args.port) |
| 43 | if port < 1 or port > 65535: |
| 44 | ctx.output.error("refused: port must be between 1 and 65535") |
| 45 | return 2 |
| 46 | |
| 47 | if not port_is_available(bind, port): |
| 48 | ctx.output.error(f"refused: port {port} is already in use") |
| 49 | return 2 |
| 50 | |
| 51 | open_browser = bool(args.open) |
| 52 | captured: dict[str, str] = {} |
| 53 | |
| 54 | def on_ready(config, _addr: str) -> None: |
| 55 | captured["session"] = config.session_credential |
| 56 | captured["csrf"] = config.csrf_token |
| 57 | _print_startup_banner(bind, port, config.session_credential, config.csrf_token) |
| 58 | if open_browser: |
| 59 | host = "127.0.0.1" if bind in {"127.0.0.1", "localhost"} else "[::1]" |
| 60 | webbrowser.open(f"http://{host}:{port}/") |
| 61 | |
| 62 | return run_app_server(repo_root=repo_root, bind=bind, port=port, ctx=ctx, on_ready=on_ready) |
File History
1 commit
sha256:8d7f41aafae41deee70035a92f93602ef1722a290153597451e5932af503a42c
docs: queue board-identity follow-ups so they survive the session
Human
59 minutes ago