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

@@ -1,6 +1,5 @@
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
"""
Execution context management for Falyx CLI actions.
"""Context management for Falyx CLI.
This module defines `ExecutionContext` and `SharedContext`, which are responsible for
capturing per-action and cross-action metadata during CLI workflow execution. These
@@ -26,6 +25,7 @@ from pydantic import BaseModel, ConfigDict, Field
from rich.console import Console
from falyx.console import console
from falyx.mode import FalyxMode
class ExecutionContext(BaseModel):
@@ -285,6 +285,30 @@ class SharedContext(BaseModel):
)
class InvocationContext(BaseModel):
program: str = ""
typed_path: list[str] = Field(default_factory=list)
mode: FalyxMode = FalyxMode.MENU
is_preview: bool = False
@property
def is_cli_mode(self) -> bool:
return self.mode != FalyxMode.MENU
def child(self, token: str) -> InvocationContext:
return InvocationContext(
program=self.program,
typed_path=[*self.typed_path, token],
mode=self.mode,
is_preview=self.is_preview,
)
def display_path(self) -> str:
if self.is_cli_mode:
return " ".join([self.program, *self.typed_path]).strip()
return " ".join(self.typed_path).strip()
if __name__ == "__main__":
import asyncio