From e7aed9ddf632c0d5f6e40018793d839fdd509d70 Mon Sep 17 00:00:00 2001 From: HumbleTurtle Date: Sun, 10 May 2026 07:38:39 -0300 Subject: [PATCH] Purge legacy CLI command aliases --- resources/scripts/nap_install.py | 52 +++++------------ resources/scripts/napseer_mcp_server.py | 56 +++++-------------- scripts/nap_install.py | 52 +++++------------ tests/test_nap_install_gateway_routes.py | 26 +++++++++ .../test_project_encryption_enable_payload.py | 2 +- 5 files changed, 68 insertions(+), 120 deletions(-) diff --git a/resources/scripts/nap_install.py b/resources/scripts/nap_install.py index ce6f7c6..357eb58 100644 --- a/resources/scripts/nap_install.py +++ b/resources/scripts/nap_install.py @@ -268,40 +268,23 @@ def preferred_state_dir(): return pathlib.Path.cwd() / ".napseer" -def legacy_state_dir(): - return pathlib.Path.cwd() / "napseer" - - def cwd_state_dir(): preferred = preferred_state_dir() - legacy = legacy_state_dir() - path = preferred if preferred.exists() or not legacy.exists() else legacy - path.mkdir(exist_ok=True) - chmod_best_effort(path, 0o700) - return path + preferred.mkdir(exist_ok=True) + chmod_best_effort(preferred, 0o700) + return preferred def state_dir_status(): preferred = preferred_state_dir() - legacy = legacy_state_dir() active = cwd_state_dir() - if preferred.exists() and legacy.exists(): - message = "Both .napseer and legacy napseer directories exist; .napseer is active." - elif active == legacy: - message = "Legacy napseer directory is active because .napseer does not exist." - else: - message = ".napseer directory is active." return { "active_state_dir": str(active), "active_auth_path": str(active / "auth.json"), - "preferred_state_dir": str(preferred), - "preferred_auth_path": str(preferred / "auth.json"), - "legacy_state_dir": str(legacy), - "legacy_auth_path": str(legacy / "auth.json"), - "preferred_state_dir_exists": preferred.exists(), - "legacy_state_dir_exists": legacy.exists(), - "legacy_state_dir_active": active == legacy, - "state_dir_message": message, + "state_dir": str(preferred), + "auth_path": str(preferred / "auth.json"), + "state_dir_exists": preferred.exists(), + "state_dir_message": ".napseer directory is active.", } @@ -631,7 +614,7 @@ def handle_gateway(args): return print_json(list_service("gateway")) or 0 if subcommand in {"logs", "log"}: return print_json(service_logs("gateway", rest)) or 0 - if subcommand in {"configure", "config"}: + if subcommand == "configure": return run_wrapper("gateway", ["configure", *rest]) if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}: return run_wrapper("gateway", [subcommand, *rest]) @@ -653,22 +636,16 @@ def main(argv): result = install_assets(update_project=True) print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."}) return 0 - if command == "bootstrap": - raise RuntimeError("nap bootstrap was removed; use nap project create") - if command == "ui": - raise RuntimeError("top-level nap ui was removed; use project and gateway commands instead") if command == "gateway": return handle_gateway(args) - if command in {"configure", "config", "status"}: + if command == "status": return run_wrapper("configure", args) if command == "project": if not args: return run_wrapper("project", ["status"]) - if args and args[0] == "bootstrap": - raise RuntimeError("nap project bootstrap was removed; use nap project create") + if args[0] == "bootstrap": + raise RuntimeError("unknown project command: bootstrap") return run_wrapper("project", args) - if command == "create": - raise RuntimeError("top-level nap create was removed; use nap project create") if command == "agent": return run_wrapper("agent", args) if command == "auth": @@ -696,10 +673,9 @@ def main(argv): print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]") print(" auth Auth commands: login-local, status.") print(" Example: nap auth login-local") - print(" project Project commands: create, status, encryption status, plaintext.") - print(" Examples: nap project create, nap project status, nap project encryption set plaintext") - print(" Direct encrypted project enablement is disabled; use gateway vault setup flows.") - print(" Equivalent explicit form: nap project encryption set plaintext") + print(" project Project commands: create, status, encryption.") + print(" Examples: nap project create, nap project status, nap project encryption status") + print(" Encrypted project setup is completed by an unlocked gateway: nap gateway vault process") print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.") print(" Start auto-selects a local port; use --port to pin one.") print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat") diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index d29afd9..94a3987 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -170,31 +170,16 @@ class RelaySocketClosed(RuntimeError): def local_state_dir(): - preferred = pathlib.Path.cwd() / ".napseer" - legacy = pathlib.Path.cwd() / "napseer" - if preferred.exists() or not legacy.exists(): - return preferred - return legacy + return pathlib.Path.cwd() / ".napseer" def state_dir_status_payload(): - preferred = pathlib.Path.cwd() / ".napseer" - legacy = pathlib.Path.cwd() / "napseer" active = AUTH_DIR - if preferred.exists() and legacy.exists(): - message = "Both .napseer and legacy napseer directories exist; .napseer is active." - elif active == legacy: - message = "Legacy napseer directory is active because .napseer does not exist." - else: - message = ".napseer directory is active." return { "active_state_dir": str(active), - "preferred_state_dir": str(preferred), - "legacy_state_dir": str(legacy), - "legacy_state_dir_active": active == legacy, - "preferred_state_dir_exists": preferred.exists(), - "legacy_state_dir_exists": legacy.exists(), - "state_dir_message": message, + "state_dir": str(active), + "state_dir_exists": active.exists(), + "state_dir_message": ".napseer directory is active.", } @@ -6722,7 +6707,7 @@ def bootstrap_project(args): if encryption == "encrypted": raise RuntimeError( "CLI direct project encryption enable is disabled for backend-owned HashiCorp project secrets. " - "Use the backend/gateway setup-request flow when available." + "Create the project, configure/unlock the gateway vault, then run `nap gateway vault process`." ) project, project_status, message = create_project_with_state(args) encryption_state = "plaintext" @@ -6751,17 +6736,7 @@ def project_encryption_transition(args=None): args = args or {} requested = str(args.get("state") or args.get("mode") or "").strip().lower() aliases = { - "on": "encrypted", - "enable": "encrypted", - "enabled": "encrypted", - "encrypt": "encrypted", "encrypted": "encrypted", - "off": "plaintext", - "disable": "plaintext", - "disabled": "plaintext", - "decrypt": "plaintext", - "decrypted": "plaintext", - "plain": "plaintext", "plaintext": "plaintext", } desired = aliases.get(requested) @@ -6770,7 +6745,7 @@ def project_encryption_transition(args=None): if desired == "encrypted": raise RuntimeError( "CLI direct project encryption enable is disabled for backend-owned HashiCorp project secrets. " - "Use the backend/gateway setup-request flow when available." + "Create the project, configure/unlock the gateway vault, then run `nap gateway vault process`." ) project_id = resolve_project_id(args) operation = "disable" @@ -9598,8 +9573,8 @@ def start_local_ui(args): - - + +

Project encryption is configured after project creation from an unlocked gateway with nap gateway vault process.

""") return search = query.get("q", [""])[0] @@ -11548,7 +11523,7 @@ def handle(message): def cli_main(argv): command = argv[1] if len(argv) > 1 else "" - if command in {"configure", "config"}: + if command == "configure": print(json.dumps(cli_configure_status(), indent=2)) return if command in {"open-ui", "ui"}: @@ -11599,12 +11574,10 @@ def cli_main(argv): if subcommand in {"claim", "login"}: print(json.dumps(cli_project_claim(argv[3:]), indent=2)) return - if subcommand == "bootstrap": - raise RuntimeError("project bootstrap was removed; use nap project create") if subcommand in {"status", "current"}: print(json.dumps(cli_configure_status(), indent=2)) return - if subcommand in {"encryption", "privacy"}: + if subcommand == "encryption": action = argv[3] if len(argv) > 3 else "status" if action in {"status", "state", "show"}: print(json.dumps(project_encryption_status({}), indent=2)) @@ -11622,10 +11595,7 @@ def cli_main(argv): "passphrase": cli_option(argv[4:], "--passphrase", default=None), }), indent=2)) return - if subcommand in {"plaintext", "plain", "unencrypt", "decrypt", "disable-encryption"}: - print(json.dumps(project_encryption_transition({"state": "plaintext"}), indent=2)) - return - print("Usage: napseer_mcp_server.py project [create [slug] [--name NAME] [--description TEXT] [--encryption plaintext] [--passphrase TEXT]|claim [--no-browser] [--timeout SECONDS] [--return-url URL]|status|encryption [status|set plaintext|plaintext] [--passphrase TEXT]|plaintext [--passphrase TEXT]]") + print("Usage: napseer_mcp_server.py project [create [slug] [--name NAME] [--description TEXT] [--encryption plaintext]|claim [--no-browser] [--timeout SECONDS] [--return-url URL]|status|encryption [status|set plaintext|plaintext]]") return if command == "gateway": subcommand = argv[2] if len(argv) > 2 else "status" @@ -11772,10 +11742,10 @@ def cli_main(argv): if action in {"list", "ls", "status"}: print(json.dumps(gateway_vault_status(payload), indent=2)) return - if action in {"process", "complete", "run"}: + if action == "process": print(json.dumps(gateway_process_vault_setup_requests(payload), indent=2)) return - if action in {"rotate", "rotate-secret"}: + if action == "rotate-secret": if not payload.get("secret_kind"): payload["secret_kind"] = vault_args[0] if vault_args and not vault_args[0].startswith("-") else "chat" print(json.dumps(gateway_rotate_project_vault_secret(payload), indent=2)) diff --git a/scripts/nap_install.py b/scripts/nap_install.py index ce6f7c6..357eb58 100644 --- a/scripts/nap_install.py +++ b/scripts/nap_install.py @@ -268,40 +268,23 @@ def preferred_state_dir(): return pathlib.Path.cwd() / ".napseer" -def legacy_state_dir(): - return pathlib.Path.cwd() / "napseer" - - def cwd_state_dir(): preferred = preferred_state_dir() - legacy = legacy_state_dir() - path = preferred if preferred.exists() or not legacy.exists() else legacy - path.mkdir(exist_ok=True) - chmod_best_effort(path, 0o700) - return path + preferred.mkdir(exist_ok=True) + chmod_best_effort(preferred, 0o700) + return preferred def state_dir_status(): preferred = preferred_state_dir() - legacy = legacy_state_dir() active = cwd_state_dir() - if preferred.exists() and legacy.exists(): - message = "Both .napseer and legacy napseer directories exist; .napseer is active." - elif active == legacy: - message = "Legacy napseer directory is active because .napseer does not exist." - else: - message = ".napseer directory is active." return { "active_state_dir": str(active), "active_auth_path": str(active / "auth.json"), - "preferred_state_dir": str(preferred), - "preferred_auth_path": str(preferred / "auth.json"), - "legacy_state_dir": str(legacy), - "legacy_auth_path": str(legacy / "auth.json"), - "preferred_state_dir_exists": preferred.exists(), - "legacy_state_dir_exists": legacy.exists(), - "legacy_state_dir_active": active == legacy, - "state_dir_message": message, + "state_dir": str(preferred), + "auth_path": str(preferred / "auth.json"), + "state_dir_exists": preferred.exists(), + "state_dir_message": ".napseer directory is active.", } @@ -631,7 +614,7 @@ def handle_gateway(args): return print_json(list_service("gateway")) or 0 if subcommand in {"logs", "log"}: return print_json(service_logs("gateway", rest)) or 0 - if subcommand in {"configure", "config"}: + if subcommand == "configure": return run_wrapper("gateway", ["configure", *rest]) if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}: return run_wrapper("gateway", [subcommand, *rest]) @@ -653,22 +636,16 @@ def main(argv): result = install_assets(update_project=True) print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."}) return 0 - if command == "bootstrap": - raise RuntimeError("nap bootstrap was removed; use nap project create") - if command == "ui": - raise RuntimeError("top-level nap ui was removed; use project and gateway commands instead") if command == "gateway": return handle_gateway(args) - if command in {"configure", "config", "status"}: + if command == "status": return run_wrapper("configure", args) if command == "project": if not args: return run_wrapper("project", ["status"]) - if args and args[0] == "bootstrap": - raise RuntimeError("nap project bootstrap was removed; use nap project create") + if args[0] == "bootstrap": + raise RuntimeError("unknown project command: bootstrap") return run_wrapper("project", args) - if command == "create": - raise RuntimeError("top-level nap create was removed; use nap project create") if command == "agent": return run_wrapper("agent", args) if command == "auth": @@ -696,10 +673,9 @@ def main(argv): print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]") print(" auth Auth commands: login-local, status.") print(" Example: nap auth login-local") - print(" project Project commands: create, status, encryption status, plaintext.") - print(" Examples: nap project create, nap project status, nap project encryption set plaintext") - print(" Direct encrypted project enablement is disabled; use gateway vault setup flows.") - print(" Equivalent explicit form: nap project encryption set plaintext") + print(" project Project commands: create, status, encryption.") + print(" Examples: nap project create, nap project status, nap project encryption status") + print(" Encrypted project setup is completed by an unlocked gateway: nap gateway vault process") print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.") print(" Start auto-selects a local port; use --port to pin one.") print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat") diff --git a/tests/test_nap_install_gateway_routes.py b/tests/test_nap_install_gateway_routes.py index 5d5ece1..434b4fe 100644 --- a/tests/test_nap_install_gateway_routes.py +++ b/tests/test_nap_install_gateway_routes.py @@ -33,6 +33,13 @@ def run(): assert mod.handle_gateway(["vault", "rotate-secret", "--kind", "chat"]) == 0 assert calls[-1] == ("gateway", ["vault", "rotate-secret", "--kind", "chat"]) + try: + mod.handle_gateway(["config"]) + except RuntimeError as exc: + assert "unknown gateway command" in str(exc) + else: + raise AssertionError("gateway config alias must not route") + try: mod.handle_gateway(["vault-setup", "list"]) except RuntimeError as exc: @@ -43,6 +50,25 @@ def run(): assert mod.handle_gateway(["terminal", "list"]) == 0 assert calls[-1] == ("gateway", ["terminal", "list"]) + calls.clear() + assert mod.main(["nap", "status"]) == 0 + assert calls[-1] == ("configure", []) + + for argv in ( + ["nap", "config"], + ["nap", "configure"], + ["nap", "bootstrap"], + ["nap", "create"], + ["nap", "ui"], + ["nap", "project", "bootstrap"], + ): + try: + mod.main(argv) + except RuntimeError as exc: + assert "unknown" in str(exc) + else: + raise AssertionError(f"deprecated route must not be accepted: {argv}") + print("ok: nap installer gateway route smoke passed") diff --git a/tests/test_project_encryption_enable_payload.py b/tests/test_project_encryption_enable_payload.py index fa7351c..ad4384a 100644 --- a/tests/test_project_encryption_enable_payload.py +++ b/tests/test_project_encryption_enable_payload.py @@ -23,7 +23,7 @@ def assert_raises_hashicorp_closed(fn): message = str(exc) assert "HashiCorp" in message assert "disabled" in message - assert "setup-request" in message + assert "nap gateway vault process" in message else: raise AssertionError("CLI direct project encryption enable must fail closed")