fix(cli): consolidate CLI dispatch surface and add nap project init

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.
This commit is contained in:
HumbleTurtle
2026-06-15 14:10:39 -03:00
parent 9ea082c919
commit e1a4cf93f0
5 changed files with 695 additions and 137 deletions
+60 -41
View File
@@ -3,12 +3,13 @@
Usage:
python3 nap_install.py
nap project init
nap project create
nap project status
nap gateway start
nap gateway stop
nap service start
nap service status
nap service logs
nap gateway configure
nap gateway logs
nap status
nap agent list
nap update
@@ -231,7 +232,7 @@ def install_assets(update_project=False):
},
"updated_project_paths": updated_project_paths,
"path_warning": path_warning(),
"next": ["nap project create", "nap gateway configure", "nap gateway start", "nap gateway logs", "nap update"],
"next": ["nap project init", "nap service start", "nap gateway configure", "nap update"],
}
return result
@@ -588,41 +589,57 @@ def service_logs(kind, args):
def handle_gateway(args):
subcommand = args[0] if args else "ls"
subcommand = args[0] if args else "help"
rest = args[1:] if args else []
if subcommand in {"help", "-h", "--help"}:
print("Usage: nap gateway [start|stop|status|logs|configure|setup|restart|kill|vault|terminal|schedule|service]")
print(" nap gateway start [--port PORT] Start the local gateway service.")
print(" nap gateway status Show the managed gateway process.")
if subcommand in {"help", "-h", "--help", "ls", "list", "status"}:
print("Usage: nap gateway [configure|setup|restart|kill|vault|terminal|schedule|service]")
print(" nap gateway configure [--command CMD] Configure the local gateway command.")
print(" nap gateway setup Create or migrate local gateway storage.")
print(" nap gateway vault Show gateway vault status and pending setup requests.")
print(" nap gateway vault process Complete pending setup requests with the vault/content passphrase.")
print(" nap gateway vault rotate-secret --kind memory")
print(" nap chat secret setup Configure chat secret state with the vault/content passphrase.")
return 0
if subcommand == "vault" and rest and rest[0] in {"help", "-h", "--help"}:
print("Usage: nap gateway vault [status|list|process|rotate-secret] [--kind memory] [--all] [--project-id ID] [--vault-passphrase TEXT]")
print(" status Show local gateway state and pending setup requests.")
print(" process Upload opaque client-wrapped key bundle records for backend-owned HashiCorp storage.")
print(" rotate-secret Rotate the memory project secret from the gateway worker path.")
print(" Chat secrets use: nap chat secret setup|status|rotate; setup uses the same vault/content passphrase.")
print(" nap gateway vault ... Show gateway vault status and pending setup requests.")
print(" nap gateway terminal ... Manage gateway tmux terminals.")
print(" nap gateway schedule ... Manage gateway schedules (cron jobs).")
print(" nap gateway service preregister|activate Register or activate a gateway service token.")
print()
print("Service lifecycle (start/stop/status/logs) lives under `nap service`.")
print("Use `nap service start|stop|status|logs` for the local gateway process.")
return 0
if subcommand in {"configure", "setup", "restart", "kill", "vault", "terminal", "schedule", "service"}:
return run_wrapper("gateway", [subcommand, *rest])
if subcommand in {"start", "stop", "logs", "log"}:
raise RuntimeError(
f"unknown gateway subcommand: {subcommand}. "
"Service lifecycle moved to `nap service <verb>`. "
f"Use `nap service {subcommand if subcommand != 'log' else 'logs'}`."
)
if subcommand == "process":
return run_wrapper("gateway", ["vault", "process", *rest])
raise RuntimeError(
"unknown gateway subcommand: process. Use `nap gateway vault process`."
)
raise RuntimeError("unknown gateway subcommand. Use `nap gateway help`.")
def handle_service(args):
subcommand = args[0] if args else "status"
rest = args[1:] if args else []
if subcommand in {"help", "-h", "--help"}:
print("Usage: nap service [start|stop|status|logs] [--port PORT] [--lines N]")
print(" start Start the local gateway service in this directory.")
print(" stop Stop the local gateway service.")
print(" status Show the managed gateway process state.")
print(" logs Tail the managed gateway log file.")
return 0
if subcommand == "start":
return print_json(start_service("gateway", ["--no-browser", *rest])) or 0
if subcommand == "stop":
return print_json(stop_service("gateway")) or 0
if subcommand in {"ls", "list", "status"}:
if subcommand == "status":
return print_json(list_service("gateway")) or 0
if subcommand in {"logs", "log"}:
return print_json(service_logs("gateway", rest)) or 0
if subcommand == "configure":
return run_wrapper("gateway", ["configure", *rest])
if subcommand in {"setup", "restart", "kill", "vault", "terminal", "schedule", "service"}:
return run_wrapper("gateway", [subcommand, *rest])
raise RuntimeError("unknown gateway command; use nap gateway start|stop|configure|setup|restart|kill|logs|status|vault|terminal|schedule|service")
if subcommand in {"ls", "list"}:
raise RuntimeError(
f"unknown service subcommand: {subcommand}. Use one of: start, stop, status, logs."
)
raise RuntimeError("unknown service subcommand. Use `nap service help`.")
def print_json(value):
@@ -636,12 +653,10 @@ def main(argv):
if command == "install":
print_json(install_assets(update_project=False))
return 0
if command == "update":
result = install_assets(update_project=True)
print_json({**result, "status": "updated", "message": "Napseer CLI and runtime scripts are updated."})
return 0
if command == "gateway":
return handle_gateway(args)
if command == "service":
return handle_service(args)
if command == "chat":
return run_wrapper("chat", args or ["secret", "status"])
if command == "status":
@@ -649,8 +664,6 @@ def main(argv):
if command == "project":
if not args:
return run_wrapper("project", ["status"])
if args[0] == "bootstrap":
raise RuntimeError("unknown project command: bootstrap")
return run_wrapper("project", args)
if command == "lineage":
return run_wrapper("lineage", args or ["status"])
@@ -662,6 +675,8 @@ def main(argv):
return run_wrapper("auth", args)
if command == "feedback":
return run_wrapper("feedback", args)
if command == "agent":
return run_wrapper("agent", args)
if command == "where":
state = state_dir_status()
print_json(
@@ -680,11 +695,13 @@ def main(argv):
)
return 0
if command in {"help", "-h", "--help"}:
print("Usage: nap [auth|project|chat|plan|lineage|gateway|agent|feedback|status|update|where|install]")
print("Usage: nap [auth|project|chat|plan|lineage|gateway|service|agent|feedback|status|update|where|install]")
print(" auth Auth commands: login-local, status.")
print(" Example: nap auth login-local")
print(" project Project commands: init/attach existing, create, status, encryption.")
print(" Examples: nap project init, nap project create, nap project status")
print(" project Project commands: init, attach, create, claim, status, encryption.")
print(" First-time setup: nap project init (enrolls and creates a project, no browser).")
print(" Attach an existing project to a Napseer account: nap project attach (opens browser).")
print(" Upgrade an anonymous project to a claimed one: nap project claim.")
print(" Encrypted project setup is completed by the local gateway: nap gateway vault process")
print(" chat Chat secret commands: secret status, secret setup, secret rotate.")
print(" Examples: nap chat secret setup --vault-passphrase TEXT, nap chat secret status")
@@ -692,13 +709,15 @@ def main(argv):
print(" Example: nap plan to-kanban /plans/example --column todo")
print(" lineage Generic source-record -> plan -> kanban lineage checks.")
print(" Example: nap lineage status")
print(" gateway Gateway commands: start, stop, configure, setup, 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 process --all")
print(" gateway Gateway operations: configure, setup, restart, kill, vault, terminal, schedule, service.")
print(" Examples: nap gateway configure, nap gateway vault process --all")
print(" service Local gateway service lifecycle: start, stop, status, logs.")
print(" Start auto-selects a port; pass --port to pin one.")
print(" Examples: nap service start, nap service logs --lines 200")
print(" agent Agent commands: list, workspaces, create, show, edit.")
print(" list shows registered project agents; workspaces shows /agents folders.")
print(" feedback Feedback commands: list, global, status, resolve.")
print(" Examples: nap feedback list, nap feedback global --all, nap feedback resolve <id> --notes TEXT")
print(" feedback Feedback commands: list, global, set, resolve.")
print(" Examples: nap feedback list, nap feedback set <id> --status open, nap feedback resolve <id> --notes TEXT")
print(" status Show cwd Napseer configuration and next setup commands.")
print(" update Refresh the globally installed nap command and wrapper.")
print(" where Show installed nap paths and cwd auth location.")