@click.command()
@click.option(
"--frontend-port", default=5173, show_default=True, type=click.IntRange(1, 65535)
)
@click.option(
"--api-port", default=8000, show_default=True, type=click.IntRange(1, 65535)
)
@click.option("--no-server", is_flag=True, help="Do not start the API server.")
@click.option("--no-browser", is_flag=True, help="Only start the frontend.")
def gui(frontend_port: int, api_port: int, no_server: bool, no_browser: bool) -> None:
"""Start the browser-based graphical mode in the default browser.
This command is intended for source checkouts. For an installed desktop
application, launch OpenJarvis from the operating system menu instead.
"""
console = Console(stderr=True)
frontend = _frontend_dir()
if frontend is None:
raise click.ClickException(
"The graphical frontend is not available in this installation. "
"Download the OpenJarvis desktop app or run this command "
"from a source checkout."
)
npm = shutil.which("npm") or shutil.which("npm.cmd")
if npm is None:
raise click.ClickException(
"Node.js/npm is required for graphical mode. Install Node.js 22 or newer."
)
_check_frontend_port(frontend_port)
_ensure_frontend_dependencies(frontend, npm)
if not no_server:
uv = shutil.which("uv")
if uv is None:
raise click.ClickException(
"uv is required to start the API with desktop dependencies. "
"Install uv or use --no-server with an already-running API."
)
server = subprocess.run(
[
uv,
"run",
"--extra",
"desktop",
"jarvis",
"start",
"--port",
str(api_port),
],
cwd=frontend.parent,
check=False,
)
if server.returncode != 0:
raise click.ClickException("Could not start the OpenJarvis API server.")
env = os.environ.copy()
# Let browser requests use Vite's same-origin proxy at any frontend port.
# VITE_API_URL is exposed to browser code, so clear an inherited override.
env["VITE_API_URL"] = ""
env["OPENJARVIS_VITE_PROXY_TARGET"] = f"http://127.0.0.1:{api_port}"
process = subprocess.Popen(
[
npm,
"run",
"dev",
"--",
"--host",
"127.0.0.1",
"--port",
str(frontend_port),
"--strictPort",
],
cwd=frontend,
env=env,
)
if not _wait_for_port(process, "127.0.0.1", frontend_port):
process.terminate()
process.wait()
raise click.ClickException(
"The graphical frontend did not start on the requested port."
)
url = f"http://127.0.0.1:{frontend_port}"
console.print(f"[green]OpenJarvis graphical mode is ready:[/green] {url}")
if not no_browser:
webbrowser.open(url)
try:
process.wait()
except KeyboardInterrupt:
process.terminate()
process.wait()