diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 4025929559..be2e59c806 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -6342,10 +6342,15 @@ def _cmd_migrate_legacy(args): remove_legacy_hermes_units(interactive=not yes, dry_run=dry_run) +def _cmd_migrate(args): + from hermes_cli.gateway_migrate import cmd_migrate + cmd_migrate(args) + + _GATEWAY_SUBCOMMANDS = { None: _cmd_run, "run": _cmd_run, "setup": _cmd_setup, "install": _cmd_install, "uninstall": _cmd_uninstall, "start": _cmd_start, "stop": _cmd_stop, "restart": _cmd_restart, - "status": _cmd_status, "list": _cmd_list, "migrate-legacy": _cmd_migrate_legacy, + "status": _cmd_status, "list": _cmd_list, "migrate-legacy": _cmd_migrate_legacy, "migrate": _cmd_migrate, } diff --git a/hermes_cli/profile_cmd.py b/hermes_cli/profile_cmd.py index 794391325c..fa8ce73c34 100644 --- a/hermes_cli/profile_cmd.py +++ b/hermes_cli/profile_cmd.py @@ -208,7 +208,12 @@ def _profile_create(args): print("\nNext steps:") print(f" {name} setup Configure API keys and model") print(f" {name} chat Start chatting") - print(f" {name} gateway start Start the messaging gateway") + from hermes_cli.gateway_multiplex_served import live_default_gateway_pid, recorded_served_profiles + if live_default_gateway_pid() is not None and recorded_served_profiles() is not None: + # The multiplexer snapshots the profile set at startup: a new profile is served only after a restart. + print(" hermes gateway restart Serve this profile from the running multiplexed gateway") + else: + print(f" {name} gateway start Start the messaging gateway") if clone or clone_all: print(f"\n Edit {profile_dir_display}/.env for different API keys") print(f" Edit {profile_dir_display}/SOUL.md for different personality") diff --git a/hermes_cli/subcommands/gateway.py b/hermes_cli/subcommands/gateway.py index c135f80301..017b933b0c 100644 --- a/hermes_cli/subcommands/gateway.py +++ b/hermes_cli/subcommands/gateway.py @@ -129,6 +129,19 @@ def build_gateway_parser( help="List what would be removed without doing it") _flag(gateway_migrate_legacy, "-y", "--yes", dest="yes", help="Skip the confirmation prompt") + gateway_migrate = gateway_subparsers.add_parser( + "migrate", help="Move per-profile gateways onto one multiplexed default gateway (or back)", + description="Stop and uninstall each secondary profile's standalone gateway, turn on " + "gateway.multiplex_profiles on the default profile and restart its gateway so it serves " + "every profile. Runs a preflight first (duplicate bot tokens, port-binding platforms " + "without a /p// ingress) and changes nothing when blocked. " + "--standalone rolls the recorded migration back.") + mode = gateway_migrate.add_mutually_exclusive_group() + _flag(mode, "--multiplex", dest="multiplex", help="Migrate to one multiplexed gateway (default)") + _flag(mode, "--standalone", dest="standalone", help="Roll back to per-profile gateways from the recorded manifest") + _flag(gateway_migrate, "--dry-run", dest="dry_run", help="Print the plan and blockers without changing anything") + _flag(gateway_migrate, "-y", "--yes", dest="yes", help="Apply without confirmation") + # enroll: redeem a single-use connector token for the per-gateway secret + per-tenant # delivery key, written to .env. See docs/relay-connector-contract.md. EXPERIMENTAL. gateway_enroll = gateway_subparsers.add_parser("enroll",