feat: enhance help command UX, completions, and CLI tips

- Expanded help command to accept:
  - `-k/--key` for detailed help on a specific command
  - `-t/--tag` for tag-filtered listings
  - `-T/--tldr` for quick usage examples
- Updated TLDR flag to support `-T` short form and refined help text.
- Improved `_render_help()` to show contextual CLI tips after help or TLDR output.
- Adjusted completer to yield both upper and lower case completions without mutating the prefix.
- Standardized CLI tip strings in root/arg parsers to reference `help` and `preview` subcommands instead of menu `run ?` syntax.
- Passed `options_manager` to history/help/exit commands for consistency.
- Allowed `help_command` to display TLDR examples when invoked without a key.
- Added test assertions for help command key/alias consistency.
- Bumped version to 0.1.82.
This commit is contained in:
2025-08-07 19:27:59 -04:00
parent 55d581b870
commit 0417a06ee4
8 changed files with 62 additions and 22 deletions

View File

@ -366,7 +366,7 @@ class Command(BaseModel):
)
return (
f"[{self.style}]{program}[/]{command_keys}",
f"[dim]{self.description}[/dim]",
f"[dim]{self.help_text or self.description}[/dim]",
"",
)

View File

@ -120,7 +120,6 @@ class FalyxCompleter(Completer):
Yields:
Completion: Matching keys or aliases from all registered commands.
"""
prefix = prefix.upper()
keys = [self.falyx.exit_command.key]
keys.extend(self.falyx.exit_command.aliases)
if self.falyx.history_command:
@ -134,7 +133,9 @@ class FalyxCompleter(Completer):
keys.extend(cmd.aliases)
for key in keys:
if key.upper().startswith(prefix):
yield Completion(key, start_position=-len(prefix))
yield Completion(key.upper(), start_position=-len(prefix))
elif key.lower().startswith(prefix):
yield Completion(key.lower(), start_position=-len(prefix))
def _ensure_quote(self, text: str) -> str:
"""

View File

@ -291,6 +291,7 @@ class Falyx:
ignore_in_history=True,
options_manager=self.options,
program=self.program,
help_text="Exit the program.",
)
def _get_history_command(self) -> Command:
@ -301,6 +302,7 @@ class Falyx:
command_style=OneColors.DARK_YELLOW,
aliases=["HISTORY"],
program=self.program,
options_manager=self.options,
)
parser.add_argument(
"-n",
@ -387,10 +389,16 @@ class Falyx:
async def _render_help(
self, tag: str = "", key: str | None = None, tldr: bool = False
) -> None:
if tldr and not key:
if self.help_command and self.help_command.arg_parser:
self.help_command.arg_parser.render_tldr()
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
return None
if key:
_, command, args, kwargs = await self.get_command(key)
if command and tldr and command.arg_parser:
command.arg_parser.render_tldr()
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
return None
elif command and tldr and not command.arg_parser:
self.console.print(
@ -398,6 +406,7 @@ class Falyx:
)
elif command and command.arg_parser:
command.arg_parser.render_help()
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
return None
elif command and not command.arg_parser:
self.console.print(
@ -415,7 +424,7 @@ class Falyx:
]
if not commands:
self.console.print(f"'{tag}'... Nothing to show here")
return
return None
for command in commands:
usage, description, _ = command.help_signature
self.console.print(
@ -424,7 +433,8 @@ class Falyx:
(0, 2),
)
)
return
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
return None
self.console.print("[bold]help:[/bold]")
for command in self.commands.values():
@ -473,8 +483,9 @@ class Falyx:
command_key="H",
command_description="Help",
command_style=OneColors.LIGHT_YELLOW,
aliases=["?", "HELP"],
aliases=["HELP", "?"],
program=self.program,
options_manager=self.options,
)
parser.add_argument(
"-t",
@ -483,11 +494,27 @@ class Falyx:
default="",
help="Optional tag to filter commands by.",
)
parser.add_argument(
"-k",
"--key",
nargs="?",
default=None,
help="Optional command key or alias to get detailed help for.",
)
parser.add_tldr_examples(
[
("", "Show all commands."),
("-k [COMMAND]", "Show detailed help for a specific command."),
("-Tk [COMMAND]", "Show quick usage examples for a specific command."),
("--tldr", "Show these quick usage examples."),
("--tag [TAG]", "Show commands with the specified tag."),
]
)
return Command(
key="H",
aliases=["?", "HELP"],
aliases=["HELP", "?"],
description="Help",
help_text="Show this help menu",
help_text="Show this help menu.",
action=Action("Help", self._render_help),
style=OneColors.LIGHT_YELLOW,
arg_parser=parser,
@ -652,6 +679,7 @@ class Falyx:
style: str = OneColors.DARK_RED,
confirm: bool = False,
confirm_message: str = "Are you sure?",
help_text: str = "Exit the program.",
) -> None:
"""Updates the back command of the menu."""
self._validate_command_key(key)
@ -669,6 +697,7 @@ class Falyx:
ignore_in_history=True,
options_manager=self.options,
program=self.program,
help_text=help_text,
)
def add_submenu(
@ -682,7 +711,12 @@ class Falyx:
key, description, submenu.menu, style=style, simple_help_signature=True
)
if submenu.exit_command.key == "X":
submenu.update_exit_command(key="B", description="Back", aliases=["BACK"])
submenu.update_exit_command(
key="B",
description="Back",
aliases=["BACK"],
help_text="Go back to the previous menu.",
)
def add_commands(self, commands: list[Command] | list[dict]) -> None:
"""Adds a list of Command instances or config dicts."""

View File

@ -156,9 +156,9 @@ class CommandArgumentParser:
if "tldr" not in self._dest_set:
tldr = Argument(
("--tldr",),
("--tldr", "-T"),
action=ArgumentAction.TLDR,
help="Show quick usage examples and exit.",
help="Show quick usage examples.",
dest="tldr",
)
self._register_argument(tldr)
@ -1408,10 +1408,13 @@ class CommandArgumentParser:
FalyxMode.RUN_ALL,
FalyxMode.HELP,
}
is_help_command = self.aliases[0] == "HELP" and self.command_key == "H"
program = self.program or "falyx"
command = self.aliases[0] if self.aliases else self.command_key
if is_cli_mode:
if is_help_command and is_cli_mode:
command = f"[{self.command_style}]{program} help[/{self.command_style}]"
elif is_cli_mode:
command = (
f"[{self.command_style}]{program} run {command}[/{self.command_style}]"
)

View File

@ -59,7 +59,7 @@ def get_root_parser(
prog: str | None = "falyx",
usage: str | None = None,
description: str | None = "Falyx CLI - Run structured async command workflows.",
epilog: str | None = "Tip: Use 'falyx run ?' to show available commands.",
epilog: str | None = "Tip: Use 'falyx help' to show available commands.",
parents: Sequence[ArgumentParser] | None = None,
prefix_chars: str = "-",
fromfile_prefix_chars: str | None = None,
@ -178,7 +178,7 @@ def get_arg_parsers(
description: str | None = "Falyx CLI - Run structured async command workflows.",
epilog: (
str | None
) = "Tip: Use 'falyx run ?[COMMAND]' to preview any command from the CLI.",
) = "Tip: Use 'falyx preview [COMMAND]' to preview any command from the CLI.",
parents: Sequence[ArgumentParser] | None = None,
prefix_chars: str = "-",
fromfile_prefix_chars: str | None = None,
@ -240,7 +240,7 @@ def get_arg_parsers(
- Use `falyx run ?[COMMAND]` from the CLI to preview a command.
"""
if epilog is None:
epilog = f"Tip: Use '{prog} run ?' to show available commands."
epilog = f"Tip: Use '{prog} help' to show available commands."
if root_parser is None:
parser = get_root_parser(
prog=prog,
@ -281,7 +281,7 @@ def get_arg_parsers(
command_description = command.help_text or command.description
run_description.append(f"{' '*24}{command_description}")
run_epilog = (
f"Tip: Use '{prog} run ?[COMMAND]' to preview commands by their key or alias."
f"Tip: Use '{prog} preview [COMMAND]' to preview commands by their key or alias."
)
run_parser = subparsers.add_parser(
"run",
@ -377,10 +377,6 @@ def get_arg_parsers(
help_parser = subparsers.add_parser("help", help="List all available commands")
help_parser.add_argument(
"-t", "--tag", help="Filter commands by tag (case-insensitive)", default=None
)
help_parser.add_argument(
"-k",
"--key",
@ -395,6 +391,10 @@ def get_arg_parsers(
help="Show a simplified TLDR examples of a command if available",
)
help_parser.add_argument(
"-t", "--tag", help="Filter commands by tag (case-insensitive)", default=None
)
version_parser = subparsers.add_parser("version", help=f"Show {prog} version")
return FalyxParsers(

View File

@ -1 +1 @@
__version__ = "0.1.81"
__version__ = "0.1.82"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "falyx"
version = "0.1.81"
version = "0.1.82"
description = "Reliable and introspectable async CLI action framework."
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
license = "MIT"

View File

@ -6,6 +6,8 @@ from falyx import Falyx
@pytest.mark.asyncio
async def test_help_command(capsys):
flx = Falyx()
assert flx.help_command.arg_parser.aliases[0] == "HELP"
assert flx.help_command.arg_parser.command_key == "H"
await flx.run_key("H")
captured = capsys.readouterr()