Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
"""``hermes dashboard`` / ``hermes serve`` subcommand parsers.
|
||||
|
||||
``dashboard`` is the browser web UI; ``serve`` is the same gateway, headless —
|
||||
what the desktop app and remote backends run. ``serve`` also skips the web UI
|
||||
build (``headless_backend=True``): pure JSON-RPC/WS clients never load the SPA.
|
||||
Both share one handler (``cmd_dashboard`` → ``start_server``). Extracted from
|
||||
``hermes_cli/main.py:main()`` (god-file Phase 2); handler injected to avoid
|
||||
importing ``main``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _add_server_runtime_args(parser) -> None:
|
||||
"""Attach the runtime flags shared by ``dashboard`` and ``serve``.
|
||||
|
||||
Both subcommands boot the *same* ``web_server.start_server`` (the
|
||||
JSON-RPC/WebSocket gateway). ``dashboard`` opens a browser UI on top of
|
||||
it; ``serve`` is the headless backend the desktop app and remote clients
|
||||
connect to. The shared server logic lives in one place — only the
|
||||
browser-opening behavior and help framing differ.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"--port", type=int, default=9119, help="Port (default 9119, 0 for auto-assign by OS)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host", default="127.0.0.1", help="Host (default 127.0.0.1)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--insecure",
|
||||
action="store_true",
|
||||
help=(
|
||||
"DEPRECATED / NO-OP. Formerly bypassed auth on a non-loopback "
|
||||
"bind. As of the June 2026 hardening it no longer disables "
|
||||
"authentication — a public bind always requires an auth provider "
|
||||
"(password or OAuth). Bind 127.0.0.1 + tunnel to keep it local."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-build",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Skip the web UI build step and serve the existing dist directly. "
|
||||
"Useful for non-interactive contexts (Windows Scheduled Tasks, CI) "
|
||||
"where npm may not be available. Pre-build with: cd web && npm run build"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--isolated",
|
||||
action="store_true",
|
||||
help=(
|
||||
"When launched from a named profile, run a dedicated server scoped "
|
||||
"to that profile instead of routing to the machine-level server. "
|
||||
"Default behavior is unified: profile launches attach to (or start) "
|
||||
"ONE machine-level server and preselect the profile."
|
||||
),
|
||||
)
|
||||
# Internal flag set by the unified-launch re-exec (cmd_dashboard) to
|
||||
# preselect the launching profile in the SPA switcher. Hidden from --help.
|
||||
parser.add_argument(
|
||||
"--open-profile",
|
||||
dest="open_profile",
|
||||
default="",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
# Lifecycle flags — mutually exclusive with each other and with the
|
||||
# start-a-server flags above (if both are passed, --stop / --status win
|
||||
# because they exit before the server is started). The server has no
|
||||
# service manager and no PID file, so these scan the process table for
|
||||
# `hermes dashboard` / `hermes serve` cmdlines and SIGTERM them directly —
|
||||
# the same path `hermes update` uses to clean up stale servers.
|
||||
parser.add_argument(
|
||||
"--stop",
|
||||
action="store_true",
|
||||
help="Stop all running Hermes web server processes and exit",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--status",
|
||||
action="store_true",
|
||||
help="List running Hermes web server processes and exit",
|
||||
)
|
||||
|
||||
|
||||
def _configure_serve_parser(parser, *, cmd_dashboard: Callable) -> None:
|
||||
"""Attach the canonical ``serve`` arguments to *parser*.
|
||||
|
||||
Kept separate from the full subcommand tree so Desktop's hot path can parse
|
||||
only the command it launches. Both callers use this exact function, keeping
|
||||
the lean parser and normal CLI semantics in lockstep.
|
||||
"""
|
||||
_add_server_runtime_args(parser)
|
||||
# Accepted but redundant: ``serve`` is always headless. Kept so callers
|
||||
# using the legacy flag do not trip an argparse error.
|
||||
parser.add_argument("--no-open", action="store_true", help=argparse.SUPPRESS)
|
||||
parser.add_argument(
|
||||
"--ssh-session-token-file",
|
||||
dest="ssh_session_token_file",
|
||||
metavar="PATH",
|
||||
default=None,
|
||||
help="Read a one-shot Desktop SSH session token from PATH",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ssh-owner-nonce",
|
||||
dest="ssh_owner_nonce",
|
||||
metavar="NONCE",
|
||||
default=None,
|
||||
help="Identify a Desktop-owned SSH backend process",
|
||||
)
|
||||
parser.set_defaults(
|
||||
func=cmd_dashboard,
|
||||
no_open=True,
|
||||
headless_backend=True,
|
||||
command="serve",
|
||||
)
|
||||
|
||||
|
||||
def build_serve_parser(
|
||||
*,
|
||||
cmd_dashboard: Callable,
|
||||
add_help: bool = True,
|
||||
exit_on_error: bool = True,
|
||||
) -> argparse.ArgumentParser:
|
||||
"""Build the standalone parser used by the lean ``serve`` dispatch path."""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="hermes serve",
|
||||
description=(
|
||||
"Run the Hermes backend server - the JSON-RPC/WebSocket gateway the "
|
||||
"desktop app and remote clients connect to. Headless: it never opens "
|
||||
"a browser UI."
|
||||
),
|
||||
add_help=add_help,
|
||||
exit_on_error=exit_on_error,
|
||||
)
|
||||
_configure_serve_parser(parser, cmd_dashboard=cmd_dashboard)
|
||||
return parser
|
||||
|
||||
|
||||
def build_dashboard_parser(
|
||||
subparsers, *, cmd_dashboard: Callable, cmd_dashboard_register: Callable
|
||||
) -> None:
|
||||
"""Attach the ``dashboard`` and ``serve`` subcommands.
|
||||
|
||||
Both share the same backend (``cmd_dashboard`` → ``start_server``).
|
||||
``dashboard`` is the browser UI; ``serve`` is the headless backend used by
|
||||
the desktop app and remote clients. They are independent surfaces — neither
|
||||
"launches" the other — so the desktop app spawns ``serve``, never
|
||||
``dashboard``.
|
||||
"""
|
||||
# =========================================================================
|
||||
# dashboard command — the browser web UI
|
||||
# =========================================================================
|
||||
dashboard_parser = subparsers.add_parser(
|
||||
"dashboard",
|
||||
help="Start the web UI dashboard",
|
||||
description="Launch the Hermes Agent web dashboard for managing config, API keys, and sessions",
|
||||
)
|
||||
_add_server_runtime_args(dashboard_parser)
|
||||
dashboard_parser.add_argument(
|
||||
"--no-open", action="store_true", help="Don't open browser automatically"
|
||||
)
|
||||
# Backward-compat shim: older Hermes desktop app shells (<= 0.15.x) spawn the
|
||||
# backend as `hermes dashboard --no-open --tui --host ... --port ...`. The
|
||||
# `--tui` flag was removed from this subcommand in cae6b5486 (embedded chat is
|
||||
# always on now). When a user's CLI updates past that commit but their desktop
|
||||
# app binary has not, argparse used to hard-error with "unrecognized arguments:
|
||||
# --tui" and exit(2) — the backend died before becoming ready and the GUI just
|
||||
# showed "Hermes couldn't start" with no actionable cause. Accept and silently
|
||||
# ignore the flag so an old app + new CLI degrades gracefully instead of
|
||||
# bricking. Hidden from --help; safe to delete once the floor app version is
|
||||
# well past 0.16.0.
|
||||
dashboard_parser.add_argument(
|
||||
"--tui",
|
||||
action="store_true",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
dashboard_parser.set_defaults(func=cmd_dashboard)
|
||||
|
||||
# =========================================================================
|
||||
# serve command — the headless backend server
|
||||
#
|
||||
# `serve` boots the exact same gateway as `dashboard` but never opens a
|
||||
# browser. It exists so the Hermes Desktop app (and headless remote
|
||||
# backends) can launch a backend WITHOUT invoking `dashboard`: the desktop
|
||||
# app and the web dashboard are independent surfaces that merely share this
|
||||
# server, and neither should appear to launch the other.
|
||||
# =========================================================================
|
||||
serve_parser = subparsers.add_parser(
|
||||
"serve",
|
||||
help="Start the Hermes backend server (headless; powers the desktop app and remote backends)",
|
||||
description=(
|
||||
"Run the Hermes backend server — the JSON-RPC/WebSocket gateway the "
|
||||
"desktop app and remote clients connect to. Headless: it never opens "
|
||||
"a browser UI."
|
||||
),
|
||||
)
|
||||
_configure_serve_parser(serve_parser, cmd_dashboard=cmd_dashboard)
|
||||
|
||||
# `hermes dashboard register` — register a self-hosted dashboard OAuth
|
||||
# client with Nous Portal and write the client_id into ~/.hermes/.env.
|
||||
# Nested subparser so bare `hermes dashboard` keeps launching the server
|
||||
# (set_defaults(func=cmd_dashboard) above remains the default).
|
||||
dashboard_subparsers = dashboard_parser.add_subparsers(
|
||||
dest="dashboard_subcommand"
|
||||
)
|
||||
dashboard_register_parser = dashboard_subparsers.add_parser(
|
||||
"register",
|
||||
help="Register a self-hosted dashboard with Nous Portal (writes the OAuth client ID to .env)",
|
||||
description=(
|
||||
"Register this install as a self-hosted dashboard with your Nous "
|
||||
"Portal account. Creates an OAuth client, writes "
|
||||
"HERMES_DASHBOARD_OAUTH_CLIENT_ID into ~/.hermes/.env, and prints "
|
||||
"how to engage the login gate. Requires being logged in (hermes setup)."
|
||||
),
|
||||
)
|
||||
dashboard_register_parser.add_argument(
|
||||
"--name",
|
||||
default=None,
|
||||
help="Human-readable label for the dashboard (default: an auto-generated name)",
|
||||
)
|
||||
dashboard_register_parser.add_argument(
|
||||
"--redirect-uri",
|
||||
dest="redirect_uri",
|
||||
default=None,
|
||||
help=(
|
||||
"Optional public HTTPS OAuth redirect URI for the dashboard, e.g. "
|
||||
"https://hermes.example.com/auth/callback. Omit for localhost-only use."
|
||||
),
|
||||
)
|
||||
dashboard_register_parser.add_argument(
|
||||
"--portal-url",
|
||||
dest="portal_url",
|
||||
default=None,
|
||||
help=(
|
||||
"Override the Nous Portal base URL for registration (default: the "
|
||||
"portal you logged into). The access token must be valid at this "
|
||||
"portal. Also settable via HERMES_DASHBOARD_PORTAL_URL. Mainly for "
|
||||
"testing against a staging/preview portal."
|
||||
),
|
||||
)
|
||||
dashboard_register_parser.set_defaults(func=cmd_dashboard_register)
|
||||
Reference in New Issue
Block a user