Purge legacy CLI command aliases

This commit is contained in:
HumbleTurtle
2026-05-10 07:38:39 -03:00
parent 08c4096868
commit e7aed9ddf6
5 changed files with 68 additions and 120 deletions
+14 -38
View File
@@ -268,40 +268,23 @@ def preferred_state_dir():
return pathlib.Path.cwd() / ".napseer" return pathlib.Path.cwd() / ".napseer"
def legacy_state_dir():
return pathlib.Path.cwd() / "napseer"
def cwd_state_dir(): def cwd_state_dir():
preferred = preferred_state_dir() preferred = preferred_state_dir()
legacy = legacy_state_dir() preferred.mkdir(exist_ok=True)
path = preferred if preferred.exists() or not legacy.exists() else legacy chmod_best_effort(preferred, 0o700)
path.mkdir(exist_ok=True) return preferred
chmod_best_effort(path, 0o700)
return path
def state_dir_status(): def state_dir_status():
preferred = preferred_state_dir() preferred = preferred_state_dir()
legacy = legacy_state_dir()
active = cwd_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 { return {
"active_state_dir": str(active), "active_state_dir": str(active),
"active_auth_path": str(active / "auth.json"), "active_auth_path": str(active / "auth.json"),
"preferred_state_dir": str(preferred), "state_dir": str(preferred),
"preferred_auth_path": str(preferred / "auth.json"), "auth_path": str(preferred / "auth.json"),
"legacy_state_dir": str(legacy), "state_dir_exists": preferred.exists(),
"legacy_auth_path": str(legacy / "auth.json"), "state_dir_message": ".napseer directory is active.",
"preferred_state_dir_exists": preferred.exists(),
"legacy_state_dir_exists": legacy.exists(),
"legacy_state_dir_active": active == legacy,
"state_dir_message": message,
} }
@@ -631,7 +614,7 @@ def handle_gateway(args):
return print_json(list_service("gateway")) or 0 return print_json(list_service("gateway")) or 0
if subcommand in {"logs", "log"}: if subcommand in {"logs", "log"}:
return print_json(service_logs("gateway", rest)) or 0 return print_json(service_logs("gateway", rest)) or 0
if subcommand in {"configure", "config"}: if subcommand == "configure":
return run_wrapper("gateway", ["configure", *rest]) return run_wrapper("gateway", ["configure", *rest])
if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}: if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}:
return run_wrapper("gateway", [subcommand, *rest]) return run_wrapper("gateway", [subcommand, *rest])
@@ -653,22 +636,16 @@ def main(argv):
result = install_assets(update_project=True) result = install_assets(update_project=True)
print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."}) print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."})
return 0 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": if command == "gateway":
return handle_gateway(args) return handle_gateway(args)
if command in {"configure", "config", "status"}: if command == "status":
return run_wrapper("configure", args) return run_wrapper("configure", args)
if command == "project": if command == "project":
if not args: if not args:
return run_wrapper("project", ["status"]) return run_wrapper("project", ["status"])
if args and args[0] == "bootstrap": if args[0] == "bootstrap":
raise RuntimeError("nap project bootstrap was removed; use nap project create") raise RuntimeError("unknown project command: bootstrap")
return run_wrapper("project", args) return run_wrapper("project", args)
if command == "create":
raise RuntimeError("top-level nap create was removed; use nap project create")
if command == "agent": if command == "agent":
return run_wrapper("agent", args) return run_wrapper("agent", args)
if command == "auth": if command == "auth":
@@ -696,10 +673,9 @@ def main(argv):
print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]") print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]")
print(" auth Auth commands: login-local, status.") print(" auth Auth commands: login-local, status.")
print(" Example: nap auth login-local") print(" Example: nap auth login-local")
print(" project Project commands: create, status, encryption status, plaintext.") print(" project Project commands: create, status, encryption.")
print(" Examples: nap project create, nap project status, nap project encryption set plaintext") print(" Examples: nap project create, nap project status, nap project encryption status")
print(" Direct encrypted project enablement is disabled; use gateway vault setup flows.") print(" Encrypted project setup is completed by an unlocked gateway: nap gateway vault process")
print(" Equivalent explicit form: nap project encryption set plaintext")
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.") 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(" 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") print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat")
+13 -43
View File
@@ -170,31 +170,16 @@ class RelaySocketClosed(RuntimeError):
def local_state_dir(): def local_state_dir():
preferred = pathlib.Path.cwd() / ".napseer" return pathlib.Path.cwd() / ".napseer"
legacy = pathlib.Path.cwd() / "napseer"
if preferred.exists() or not legacy.exists():
return preferred
return legacy
def state_dir_status_payload(): def state_dir_status_payload():
preferred = pathlib.Path.cwd() / ".napseer"
legacy = pathlib.Path.cwd() / "napseer"
active = AUTH_DIR 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 { return {
"active_state_dir": str(active), "active_state_dir": str(active),
"preferred_state_dir": str(preferred), "state_dir": str(active),
"legacy_state_dir": str(legacy), "state_dir_exists": active.exists(),
"legacy_state_dir_active": active == legacy, "state_dir_message": ".napseer directory is active.",
"preferred_state_dir_exists": preferred.exists(),
"legacy_state_dir_exists": legacy.exists(),
"state_dir_message": message,
} }
@@ -6722,7 +6707,7 @@ def bootstrap_project(args):
if encryption == "encrypted": if encryption == "encrypted":
raise RuntimeError( raise RuntimeError(
"CLI direct project encryption enable is disabled for backend-owned HashiCorp project secrets. " "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) project, project_status, message = create_project_with_state(args)
encryption_state = "plaintext" encryption_state = "plaintext"
@@ -6751,17 +6736,7 @@ def project_encryption_transition(args=None):
args = args or {} args = args or {}
requested = str(args.get("state") or args.get("mode") or "").strip().lower() requested = str(args.get("state") or args.get("mode") or "").strip().lower()
aliases = { aliases = {
"on": "encrypted",
"enable": "encrypted",
"enabled": "encrypted",
"encrypt": "encrypted",
"encrypted": "encrypted", "encrypted": "encrypted",
"off": "plaintext",
"disable": "plaintext",
"disabled": "plaintext",
"decrypt": "plaintext",
"decrypted": "plaintext",
"plain": "plaintext",
"plaintext": "plaintext", "plaintext": "plaintext",
} }
desired = aliases.get(requested) desired = aliases.get(requested)
@@ -6770,7 +6745,7 @@ def project_encryption_transition(args=None):
if desired == "encrypted": if desired == "encrypted":
raise RuntimeError( raise RuntimeError(
"CLI direct project encryption enable is disabled for backend-owned HashiCorp project secrets. " "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) project_id = resolve_project_id(args)
operation = "disable" operation = "disable"
@@ -9598,8 +9573,8 @@ def start_local_ui(args):
<label>Project name<input name="name" required placeholder="{html.escape(default_project_name(default_project_slug()))}"></label> <label>Project name<input name="name" required placeholder="{html.escape(default_project_name(default_project_slug()))}"></label>
<label>Project slug<input name="slug" required value="{html.escape(default_project_slug())}"></label> <label>Project slug<input name="slug" required value="{html.escape(default_project_slug())}"></label>
<label>Description<textarea name="description"></textarea></label> <label>Description<textarea name="description"></textarea></label>
<label>Initial privacy<select name="encryption"><option value="plaintext">Plaintext project</option><option value="encrypted">Encrypted project</option></select></label> <input type="hidden" name="encryption" value="plaintext">
<label>Project passphrase, only for encrypted projects<input name="passphrase" type="password"></label> <p>Project encryption is configured after project creation from an unlocked gateway with <code>nap gateway vault process</code>.</p>
<button type="submit">Create project</button></form></section></main></body></html>""") <button type="submit">Create project</button></form></section></main></body></html>""")
return return
search = query.get("q", [""])[0] search = query.get("q", [""])[0]
@@ -11548,7 +11523,7 @@ def handle(message):
def cli_main(argv): def cli_main(argv):
command = argv[1] if len(argv) > 1 else "" command = argv[1] if len(argv) > 1 else ""
if command in {"configure", "config"}: if command == "configure":
print(json.dumps(cli_configure_status(), indent=2)) print(json.dumps(cli_configure_status(), indent=2))
return return
if command in {"open-ui", "ui"}: if command in {"open-ui", "ui"}:
@@ -11599,12 +11574,10 @@ def cli_main(argv):
if subcommand in {"claim", "login"}: if subcommand in {"claim", "login"}:
print(json.dumps(cli_project_claim(argv[3:]), indent=2)) print(json.dumps(cli_project_claim(argv[3:]), indent=2))
return return
if subcommand == "bootstrap":
raise RuntimeError("project bootstrap was removed; use nap project create")
if subcommand in {"status", "current"}: if subcommand in {"status", "current"}:
print(json.dumps(cli_configure_status(), indent=2)) print(json.dumps(cli_configure_status(), indent=2))
return return
if subcommand in {"encryption", "privacy"}: if subcommand == "encryption":
action = argv[3] if len(argv) > 3 else "status" action = argv[3] if len(argv) > 3 else "status"
if action in {"status", "state", "show"}: if action in {"status", "state", "show"}:
print(json.dumps(project_encryption_status({}), indent=2)) print(json.dumps(project_encryption_status({}), indent=2))
@@ -11622,10 +11595,7 @@ def cli_main(argv):
"passphrase": cli_option(argv[4:], "--passphrase", default=None), "passphrase": cli_option(argv[4:], "--passphrase", default=None),
}), indent=2)) }), indent=2))
return return
if subcommand in {"plaintext", "plain", "unencrypt", "decrypt", "disable-encryption"}: 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]]")
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]]")
return return
if command == "gateway": if command == "gateway":
subcommand = argv[2] if len(argv) > 2 else "status" subcommand = argv[2] if len(argv) > 2 else "status"
@@ -11772,10 +11742,10 @@ def cli_main(argv):
if action in {"list", "ls", "status"}: if action in {"list", "ls", "status"}:
print(json.dumps(gateway_vault_status(payload), indent=2)) print(json.dumps(gateway_vault_status(payload), indent=2))
return return
if action in {"process", "complete", "run"}: if action == "process":
print(json.dumps(gateway_process_vault_setup_requests(payload), indent=2)) print(json.dumps(gateway_process_vault_setup_requests(payload), indent=2))
return return
if action in {"rotate", "rotate-secret"}: if action == "rotate-secret":
if not payload.get("secret_kind"): if not payload.get("secret_kind"):
payload["secret_kind"] = vault_args[0] if vault_args and not vault_args[0].startswith("-") else "chat" 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)) print(json.dumps(gateway_rotate_project_vault_secret(payload), indent=2))
+14 -38
View File
@@ -268,40 +268,23 @@ def preferred_state_dir():
return pathlib.Path.cwd() / ".napseer" return pathlib.Path.cwd() / ".napseer"
def legacy_state_dir():
return pathlib.Path.cwd() / "napseer"
def cwd_state_dir(): def cwd_state_dir():
preferred = preferred_state_dir() preferred = preferred_state_dir()
legacy = legacy_state_dir() preferred.mkdir(exist_ok=True)
path = preferred if preferred.exists() or not legacy.exists() else legacy chmod_best_effort(preferred, 0o700)
path.mkdir(exist_ok=True) return preferred
chmod_best_effort(path, 0o700)
return path
def state_dir_status(): def state_dir_status():
preferred = preferred_state_dir() preferred = preferred_state_dir()
legacy = legacy_state_dir()
active = cwd_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 { return {
"active_state_dir": str(active), "active_state_dir": str(active),
"active_auth_path": str(active / "auth.json"), "active_auth_path": str(active / "auth.json"),
"preferred_state_dir": str(preferred), "state_dir": str(preferred),
"preferred_auth_path": str(preferred / "auth.json"), "auth_path": str(preferred / "auth.json"),
"legacy_state_dir": str(legacy), "state_dir_exists": preferred.exists(),
"legacy_auth_path": str(legacy / "auth.json"), "state_dir_message": ".napseer directory is active.",
"preferred_state_dir_exists": preferred.exists(),
"legacy_state_dir_exists": legacy.exists(),
"legacy_state_dir_active": active == legacy,
"state_dir_message": message,
} }
@@ -631,7 +614,7 @@ def handle_gateway(args):
return print_json(list_service("gateway")) or 0 return print_json(list_service("gateway")) or 0
if subcommand in {"logs", "log"}: if subcommand in {"logs", "log"}:
return print_json(service_logs("gateway", rest)) or 0 return print_json(service_logs("gateway", rest)) or 0
if subcommand in {"configure", "config"}: if subcommand == "configure":
return run_wrapper("gateway", ["configure", *rest]) return run_wrapper("gateway", ["configure", *rest])
if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}: if subcommand in {"setup", "unlock", "lock", "restart", "kill", "vault", "terminal", "schedule", "service"}:
return run_wrapper("gateway", [subcommand, *rest]) return run_wrapper("gateway", [subcommand, *rest])
@@ -653,22 +636,16 @@ def main(argv):
result = install_assets(update_project=True) result = install_assets(update_project=True)
print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."}) print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."})
return 0 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": if command == "gateway":
return handle_gateway(args) return handle_gateway(args)
if command in {"configure", "config", "status"}: if command == "status":
return run_wrapper("configure", args) return run_wrapper("configure", args)
if command == "project": if command == "project":
if not args: if not args:
return run_wrapper("project", ["status"]) return run_wrapper("project", ["status"])
if args and args[0] == "bootstrap": if args[0] == "bootstrap":
raise RuntimeError("nap project bootstrap was removed; use nap project create") raise RuntimeError("unknown project command: bootstrap")
return run_wrapper("project", args) return run_wrapper("project", args)
if command == "create":
raise RuntimeError("top-level nap create was removed; use nap project create")
if command == "agent": if command == "agent":
return run_wrapper("agent", args) return run_wrapper("agent", args)
if command == "auth": if command == "auth":
@@ -696,10 +673,9 @@ def main(argv):
print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]") print("Usage: nap [auth|project|gateway|agent|feedback|status|update|where|install]")
print(" auth Auth commands: login-local, status.") print(" auth Auth commands: login-local, status.")
print(" Example: nap auth login-local") print(" Example: nap auth login-local")
print(" project Project commands: create, status, encryption status, plaintext.") print(" project Project commands: create, status, encryption.")
print(" Examples: nap project create, nap project status, nap project encryption set plaintext") print(" Examples: nap project create, nap project status, nap project encryption status")
print(" Direct encrypted project enablement is disabled; use gateway vault setup flows.") print(" Encrypted project setup is completed by an unlocked gateway: nap gateway vault process")
print(" Equivalent explicit form: nap project encryption set plaintext")
print(" gateway Gateway commands: start, stop, configure, setup, unlock, lock, restart, kill, logs, status, vault.") 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(" 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") print(" Examples: nap gateway start, nap gateway vault, nap gateway vault rotate-secret --kind chat")
+26
View File
@@ -33,6 +33,13 @@ def run():
assert mod.handle_gateway(["vault", "rotate-secret", "--kind", "chat"]) == 0 assert mod.handle_gateway(["vault", "rotate-secret", "--kind", "chat"]) == 0
assert calls[-1] == ("gateway", ["vault", "rotate-secret", "--kind", "chat"]) 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: try:
mod.handle_gateway(["vault-setup", "list"]) mod.handle_gateway(["vault-setup", "list"])
except RuntimeError as exc: except RuntimeError as exc:
@@ -43,6 +50,25 @@ def run():
assert mod.handle_gateway(["terminal", "list"]) == 0 assert mod.handle_gateway(["terminal", "list"]) == 0
assert calls[-1] == ("gateway", ["terminal", "list"]) 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") print("ok: nap installer gateway route smoke passed")
@@ -23,7 +23,7 @@ def assert_raises_hashicorp_closed(fn):
message = str(exc) message = str(exc)
assert "HashiCorp" in message assert "HashiCorp" in message
assert "disabled" in message assert "disabled" in message
assert "setup-request" in message assert "nap gateway vault process" in message
else: else:
raise AssertionError("CLI direct project encryption enable must fail closed") raise AssertionError("CLI direct project encryption enable must fail closed")