The dispatcher in `napseer_mcp_server.py::cli_main` had grown a duplicated
fragment of the project-command dispatcher (the attach/create/claim/status/
encryption block) that survived an earlier refactor. Because the fragment
was left at function-body indent after the `return` that closes the
canonical block, every `nap` subcommand that hands off to the MCP server
crashed at import time:
File "/home/matias/.local/share/napseer/napseer_mcp_server.py",
line 13561
"frontend_url": cli_option(argv[3:], "--frontend-url",
default=None),
IndentationError: unexpected indent
That broke `nap project attach`, `nap project create`, `nap gateway
start`, and every other `nap *` command that dispatches to the MCP
server, surfacing as an "unknown command" / 401-style failure at the
caller. The pinned published revision `cbdc1840` still ships the broken
file; the next backend build needs to bump to this commit.
Rewrite `cli_main` so each `if command == "<cmd>":` block has a
single, complete dispatcher with no leftover fragments. Promote
`init` (first-time directory setup, no browser) to a canonical
`nap project init` subcommand, leave `attach` calling
`operator_loopback_login`, and `create` / `claim` unchanged.
Drop deprecated aliases (`ls`, `new`, `send`, `rm`, `set-status`,
`admin`, `global-list`, positional-state shortcut for
`nap project encryption set`, `process --all` shortcut) with clear
RuntimeError pointers to the canonical verbs.
Tests: `tests/test_cli_dispatch_consolidation.py` asserts canonical
verbs route correctly and dropped aliases raise with a clear pointer;
`tests/test_project_init_wizard.py` exercises the
`nap project init` wizard end-to-end;
`tests/test_nap_install_gateway_routes.py` updated for the
`process --all` removal. 19/19 pytest cases pass.
See /incidents/2026-06-15/napseer-mcp-server-py-13561-indentation-error-upstream
in Napseer memory for the prior incident writeup.
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Smoke-test top-level nap gateway command routing."""
|
|
|
|
import importlib.util
|
|
import pathlib
|
|
import sys
|
|
|
|
|
|
def load_module():
|
|
script_path = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "nap_install.py"
|
|
spec = importlib.util.spec_from_file_location("nap_install", script_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def run():
|
|
mod = load_module()
|
|
calls = []
|
|
|
|
mod.run_wrapper = lambda command, args: calls.append((command, args)) or 0
|
|
|
|
assert mod.handle_gateway(["--help"]) == 0
|
|
assert calls == []
|
|
|
|
assert mod.handle_gateway(["vault", "--help"]) == 0
|
|
assert calls[-1] == ("gateway", ["vault", "--help"])
|
|
|
|
assert mod.handle_gateway(["vault", "list"]) == 0
|
|
assert calls[-1] == ("gateway", ["vault", "list"])
|
|
|
|
assert mod.handle_gateway(["vault", "rotate-secret", "--kind", "memory"]) == 0
|
|
assert calls[-1] == ("gateway", ["vault", "rotate-secret", "--kind", "memory"])
|
|
|
|
try:
|
|
mod.handle_gateway(["process", "--all"])
|
|
except RuntimeError as exc:
|
|
assert "nap gateway vault process" in str(exc), str(exc)
|
|
else:
|
|
raise AssertionError("deprecated gateway process shortcut must not route")
|
|
assert mod.main(["nap", "chat", "secret", "setup"]) == 0
|
|
assert calls[-1] == ("chat", ["secret", "setup"])
|
|
try:
|
|
mod.handle_gateway(["config"])
|
|
except RuntimeError as exc:
|
|
assert "unknown gateway subcommand" in str(exc)
|
|
else:
|
|
raise AssertionError("gateway config alias must not route")
|
|
try:
|
|
mod.handle_gateway(["vault-setup", "list"])
|
|
except RuntimeError as exc:
|
|
assert "unknown gateway subcommand" in str(exc)
|
|
else:
|
|
raise AssertionError("deprecated gateway vault-setup alias must not route")
|
|
|
|
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", [])
|
|
|
|
# Install-level dispatch should reject: empty / config / configure / bootstrap / create / ui
|
|
for argv in (
|
|
["nap", "config"],
|
|
["nap", "configure"],
|
|
["nap", "bootstrap"],
|
|
["nap", "create"],
|
|
["nap", "ui"],
|
|
):
|
|
try:
|
|
mod.main(argv)
|
|
except RuntimeError as exc:
|
|
assert "unknown" in str(exc), (argv, str(exc))
|
|
else:
|
|
raise AssertionError(f"deprecated route must not be accepted at install level: {argv}")
|
|
# nap project bootstrap is now a deprecated alias handled by the MCP server
|
|
# (the install side forwards it; the MCP side raises with a clear error message).
|
|
forwarded = []
|
|
mod.run_wrapper = lambda command, args: forwarded.append((command, args)) or 0
|
|
mod.main(["nap", "project", "bootstrap"])
|
|
assert forwarded == [("project", ["bootstrap"])], forwarded
|
|
|
|
print("ok: nap installer gateway route smoke passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|