77import argparse
88import asyncio
99import os
10- import subprocess as _subprocess
11- from contextlib import suppress
1210from pathlib import Path
1311from typing import Any , NoReturn
1412
1513from loguru import logger as _log # type: ignore[reportMissingImports]
1614
1715from src .config import Config , get_config_dir , get_config_json_path
1816from src .mcp_importer .cli import run_cli
17+ from src .setup_tui .main import run_import_tui
1918from src .server import OpenEdisonProxy
2019
2120log : Any = _log
@@ -37,6 +36,17 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
3736 parser .add_argument (
3837 "--port" , type = int , help = "Server port override (FastMCP on port, FastAPI on port+1)"
3938 )
39+ # For the setup wizard
40+ parser .add_argument (
41+ "--wizard-dry-run" ,
42+ action = "store_true" ,
43+ help = "(For the setup wizard) Show changes without writing to config.json" ,
44+ )
45+ parser .add_argument (
46+ "--wizard-skip-oauth" ,
47+ action = "store_true" ,
48+ help = "(For the setup wizard) Skip OAuth for remote servers (they will be omitted from import)" ,
49+ )
4050 # Website runs from packaged assets by default; no extra website flags
4151
4252 # Subcommands (extensible)
@@ -88,89 +98,7 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
8898 return parser .parse_args (argv )
8999
90100
91- def _spawn_frontend_dev ( # noqa: C901 - pragmatic complexity for env probing
92- port : int ,
93- override_dir : Path | None = None ,
94- config_dir : Path | None = None ,
95- ) -> tuple [int , _subprocess .Popen [bytes ] | None ]:
96- """Try to start the frontend dev server by running `npm run dev`.
97-
98- Search order for working directory:
99- 1) Packaged project path: <pkg_root>/frontend
100- 2) Current working directory (if it contains a package.json)
101- """
102- candidates : list [Path ] = []
103- # Prefer packaged static assets; if present, the backend serves /dashboard
104- static_candidates = [
105- Path (__file__ ).parent / "frontend_dist" , # inside package dir
106- Path (__file__ ).parent .parent / "frontend_dist" , # site-packages root
107- ]
108- static_dir = next ((p for p in static_candidates if p .exists () and p .is_dir ()), None )
109- if static_dir is not None :
110- log .info (
111- f"Packaged dashboard detected at { static_dir } . It will be served at /dashboard by the API server."
112- )
113- # No separate website process needed. Return sentinel port (-1) so caller knows not to warn.
114- return (- 1 , None )
115-
116- if static_dir is None :
117- raise RuntimeError (
118- "No packaged dashboard detected. The website will be served from the frontend directory."
119- )
120-
121- pkg_frontend_candidates = [
122- Path (__file__ ).parent / "frontend" , # inside package dir
123- Path (__file__ ).parent .parent / "frontend" , # site-packages root
124- ]
125- if override_dir is not None :
126- candidates .append (override_dir )
127- for pf in pkg_frontend_candidates :
128- if pf .exists ():
129- candidates .append (pf )
130- if config_dir is not None and (config_dir / "package.json" ).exists ():
131- candidates .append (config_dir )
132- cwd_pkg = Path .cwd ()
133- if (cwd_pkg / "package.json" ).exists ():
134- candidates .append (cwd_pkg )
135-
136- if not candidates :
137- log .warning (
138- "No frontend directory found (no packaged frontend and no package.json in CWD). Skipping website."
139- )
140- return (port , None )
141-
142- for candidate in candidates :
143- try :
144- # If no package.json but directory exists, try a basic npm i per user request
145- if not (candidate / "package.json" ).exists ():
146- log .info (f"No package.json in { candidate } . Running 'npm i' as best effort..." )
147- _ = _subprocess .call (["npm" , "i" ], cwd = str (candidate ))
148-
149- # Install deps if needed
150- if (
151- not (candidate / "node_modules" ).exists ()
152- and (candidate / "package-lock.json" ).exists ()
153- ):
154- log .info (f"Installing frontend dependencies with npm ci in { candidate } ..." )
155- r_install = _subprocess .call (["npm" , "ci" ], cwd = str (candidate ))
156- if r_install != 0 :
157- log .error ("Failed to install frontend dependencies" )
158- continue
159-
160- log .info (f"Starting frontend dev server in { candidate } on port { port } ..." )
161- cmd_default = ["npm" , "run" , "dev" , "--" , "--port" , str (port )]
162- proc = _subprocess .Popen (cmd_default , cwd = str (candidate ))
163- return (port , proc )
164- except FileNotFoundError :
165- log .error ("npm not found. Please install Node.js to run the website dev server." )
166- return (port , None )
167-
168- # If all candidates failed
169- return (port , None )
170-
171-
172101async def _run_server (args : Any ) -> None :
173- # TODO check this works as we want it to
174102 # Resolve config dir and expose via env for the rest of the app
175103 config_dir_arg = getattr (args , "config_dir" , None )
176104 if config_dir_arg is not None :
@@ -186,44 +114,26 @@ async def _run_server(args: Any) -> None:
186114 log .info (f"Using config directory: { config_dir } " )
187115 proxy = OpenEdisonProxy (host = host , port = port )
188116
189- # Website served from packaged assets by default; still detect and log
190- frontend_proc = None
191- used_port , frontend_proc = _spawn_frontend_dev (5173 , None , config_dir )
192- if frontend_proc is None and used_port == - 1 :
193- log .info ("Frontend is being served from packaged assets at /dashboard" )
194-
195117 try :
196118 await proxy .start ()
197119 _ = await asyncio .Event ().wait ()
198120 except KeyboardInterrupt :
199121 log .info ("Received shutdown signal" )
200- finally :
201- if frontend_proc is not None :
202- with suppress (Exception ):
203- frontend_proc .terminate ()
204- _ = frontend_proc .wait (timeout = 5 )
205- with suppress (Exception ):
206- frontend_proc .kill ()
207-
208-
209- def _run_website (port : int , website_dir : Path | None = None ) -> int :
210- # Use the same spawning logic, then return 0 if started or 1 if failed
211- _ , proc = _spawn_frontend_dev (port , website_dir )
212- return 0 if proc is not None else 1
213122
214123
215124def main (argv : list [str ] | None = None ) -> NoReturn : # noqa: C901
216125 args = _parse_args (argv )
217126
218- if getattr (args , "command" , None ) == "website" :
219- exit_code = _run_website (port = args .port , website_dir = getattr (args , "dir" , None ))
220- raise SystemExit (exit_code )
127+ if args .command is None :
128+ args .command = "run"
221129
222- if getattr ( args , " command" , None ) == "import-mcp" :
130+ if args . command == "import-mcp" :
223131 result_code = run_cli (argv )
224132 raise SystemExit (result_code )
225133
226- # default: run server (top-level flags)
134+ # Run import tui if necessary
135+ run_import_tui (args )
136+
227137 try :
228138 asyncio .run (_run_server (args ))
229139 raise SystemExit (0 )
0 commit comments