feat: add recursive namespace routing and standalone runner polish

- introduce namespace-aware routing with RootParseResult, RouteResult, and InvocationContext
- register submenus as FalyxNamespace entries and resolve them through _entry_map
- refactor FalyxParser to parse only root options and leave recursive routing to Falyx
- add prepare_route, resolve_route, and route dispatch flow to Falyx
- update validator and completer to understand namespace entries and route results
- unify help/TLDR rendering APIs and add custom_tldr support on Command
- tighten Command.resolve_args error handling and parser type validation
- improve CommandRunner dependency validation and argv handling
- add BottomBar.has_items and improve wrapped executor error messages
- add tests for execution options, resolve_args, command runner, and route-aware validation
This commit is contained in:
2026-04-11 11:57:03 -04:00
parent 5d8f3aa603
commit 30cb8b97b5
26 changed files with 1658 additions and 493 deletions

View File

@@ -27,6 +27,8 @@ from typing import TYPE_CHECKING, Iterable
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.document import Document
from falyx.namespace import FalyxNamespace
if TYPE_CHECKING:
from falyx import Falyx
@@ -35,7 +37,7 @@ class FalyxCompleter(Completer):
"""Prompt Toolkit completer for Falyx CLI command input.
This completer provides real-time, context-aware suggestions for:
- Command keys and aliases (resolved via Falyx._name_map)
- Command keys and aliases (resolved via Falyx._entry_map)
- CLI argument flags and values for each command
- Suggestions and choices defined in the associated CommandArgumentParser
@@ -89,14 +91,14 @@ class FalyxCompleter(Completer):
def _resolve_command_for_completion(self, token: str):
normalized = token.upper().strip()
name_map = self.falyx._name_map
entry_map = self.falyx._entry_map
if normalized in name_map:
return name_map[normalized]
if normalized in entry_map:
return entry_map[normalized]
matches = []
seen = set()
for key, command in name_map.items():
for key, command in entry_map.items():
if key.startswith(normalized) and id(command) not in seen:
matches.append(command)
seen.add(id(command))
@@ -146,6 +148,13 @@ class FalyxCompleter(Completer):
# Identify command
command_key = tokens[0].upper()
command = self._resolve_command_for_completion(command_key)
if isinstance(command, FalyxNamespace):
completer = command.namespace._get_completer()
for completion in completer.get_completions(
Document(" ".join(tokens[1:])), complete_event
):
yield completion
return
if not command or not command.arg_parser:
return