feat(help & spinners): improve help rendering, async spinner handling, and pipeline demo
- Refactored `Command.help_signature` to return `(usage, description, tags)` instead of a Rich `Padding`/`Panel`. - Replaced `show_help()` with `render_help()` in `Command` and `Falyx`. - Updated Falyx help rendering to use Rich `Panel`/`Padding` consistently for cleaner UI. - Swapped `print()` calls for `console.print()` for styled output. - Added hooks to `ProcessAction` to announce analysis start/finish. - Added spinners to test and deploy steps; simplified retry setup. - Converted `remove()` to `async def remove()` for consistency. - Added async lock to prevent concurrent Live loop start/stop races. - Added debug logging when starting/stopping the Live loop. - Updated `spinner_teardown_hook` to `await sm.remove(...)` to align with async `remove()`. - Removed `rich.panel`/`rich.padding` from `Command` since panels are now built in `Falyx` help rendering. - Bumped `rich` dependency to `^14.0`. - Bumped version to 0.1.78. This commit polishes help display, demo UX, and spinner lifecycle safety—making spinners thread/async safe and help output more structured and readable.
This commit is contained in:
		| @@ -4,44 +4,44 @@ import time | ||||
|  | ||||
| from falyx import Falyx | ||||
| from falyx.action import Action, ActionGroup, ChainedAction, ProcessAction | ||||
| from falyx.retry import RetryHandler, RetryPolicy | ||||
| from falyx.console import console | ||||
|  | ||||
|  | ||||
| # Step 1: Fast I/O-bound setup (standard Action) | ||||
| async def checkout_code(): | ||||
|     print("📥 Checking out code...") | ||||
|     console.print("🔄 Checking out code...") | ||||
|     await asyncio.sleep(0.5) | ||||
|     console.print("📦 Code checked out successfully.") | ||||
|  | ||||
|  | ||||
| # Step 2: CPU-bound task (ProcessAction) | ||||
| def run_static_analysis(): | ||||
|     print("🧠 Running static analysis (CPU-bound)...") | ||||
|     total = 0 | ||||
|     for i in range(10_000_000): | ||||
|         total += i % 3 | ||||
|     time.sleep(5) | ||||
|     time.sleep(2) | ||||
|     return total | ||||
|  | ||||
|  | ||||
| # Step 3: Simulated flaky test with retry | ||||
| async def flaky_tests(): | ||||
|     console.print("🧪 Running tests...") | ||||
|     await asyncio.sleep(0.3) | ||||
|     if random.random() < 0.3: | ||||
|         raise RuntimeError("❌ Random test failure!") | ||||
|     print("🧪 Tests passed.") | ||||
|     console.print("🧪 Tests passed.") | ||||
|     return "ok" | ||||
|  | ||||
|  | ||||
| # Step 4: Multiple deploy targets (parallel ActionGroup) | ||||
| async def deploy_to(target: str): | ||||
|     print(f"🚀 Deploying to {target}...") | ||||
|     console.print(f"🚀 Deploying to {target}...") | ||||
|     await asyncio.sleep(random.randint(2, 6)) | ||||
|     console.print(f"✅ Deployment to {target} complete.") | ||||
|     return f"{target} complete" | ||||
|  | ||||
|  | ||||
| def build_pipeline(): | ||||
|     retry_handler = RetryHandler(RetryPolicy(max_retries=3, delay=0.5)) | ||||
|  | ||||
|     # Base actions | ||||
|     checkout = Action("Checkout", checkout_code) | ||||
|     analysis = ProcessAction( | ||||
| @@ -50,8 +50,17 @@ def build_pipeline(): | ||||
|         spinner=True, | ||||
|         spinner_message="Analyzing code...", | ||||
|     ) | ||||
|     tests = Action("Run Tests", flaky_tests) | ||||
|     tests.hooks.register("on_error", retry_handler.retry_on_error) | ||||
|     analysis.hooks.register( | ||||
|         "before", lambda ctx: console.print("🧠 Running static analysis (CPU-bound)...") | ||||
|     ) | ||||
|     analysis.hooks.register("after", lambda ctx: console.print("🧠 Analysis complete!")) | ||||
|     tests = Action( | ||||
|         "Run Tests", | ||||
|         flaky_tests, | ||||
|         retry=True, | ||||
|         spinner=True, | ||||
|         spinner_message="Running tests...", | ||||
|     ) | ||||
|  | ||||
|     # Parallel deploys | ||||
|     deploy_group = ActionGroup( | ||||
| @@ -91,14 +100,18 @@ pipeline = build_pipeline() | ||||
| # Run the pipeline | ||||
| async def main(): | ||||
|  | ||||
|     flx = Falyx() | ||||
|     flx = Falyx( | ||||
|         hide_menu_table=True, program="pipeline_demo.py", show_placeholder_menu=True | ||||
|     ) | ||||
|     flx.add_command( | ||||
|         "A", | ||||
|         "Action Thing", | ||||
|         "P", | ||||
|         "Run Pipeline", | ||||
|         pipeline, | ||||
|         spinner=True, | ||||
|         spinner_type="line", | ||||
|         spinner_message="Running pipeline...", | ||||
|         tags=["pipeline", "demo"], | ||||
|         help_text="Run the full CI/CD pipeline demo.", | ||||
|     ) | ||||
|  | ||||
|     await flx.run() | ||||
|   | ||||
| @@ -22,8 +22,6 @@ from typing import Any, Awaitable, Callable | ||||
|  | ||||
| from prompt_toolkit.formatted_text import FormattedText | ||||
| from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator | ||||
| from rich.padding import Padding | ||||
| from rich.panel import Panel | ||||
| from rich.tree import Tree | ||||
|  | ||||
| from falyx.action.action import Action | ||||
| @@ -342,7 +340,7 @@ class Command(BaseModel): | ||||
|         return f"  {command_keys_text:<20}  {options_text} " | ||||
|  | ||||
|     @property | ||||
|     def help_signature(self) -> tuple[Padding, str]: | ||||
|     def help_signature(self) -> tuple[str, str, str]: | ||||
|         """Generate a help signature for the command.""" | ||||
|         is_cli_mode = self.options_manager.get("mode") in { | ||||
|             FalyxMode.RUN, | ||||
| @@ -353,30 +351,21 @@ class Command(BaseModel): | ||||
|         program = f"{self.program} run " if is_cli_mode else "" | ||||
|  | ||||
|         if self.arg_parser and not self.simple_help_signature: | ||||
|             usage = Padding( | ||||
|                 Panel( | ||||
|                     f"[{self.style}]{program}[/]{self.arg_parser.get_usage()}", | ||||
|                     expand=False, | ||||
|                 ), | ||||
|                 (0, 2), | ||||
|             ) | ||||
|             description = [f"    [dim]{self.help_text or self.description}[/dim]"] | ||||
|             usage = f"[{self.style}]{program}[/]{self.arg_parser.get_usage()}" | ||||
|             description = f"[dim]{self.help_text or self.description}[/dim]" | ||||
|             if self.tags: | ||||
|                 description.append(f"    [dim]Tags: {', '.join(self.tags)}[/dim]") | ||||
|             return usage, "\n".join(description) | ||||
|                 tags = f"[dim]Tags: {', '.join(self.tags)}[/dim]" | ||||
|             else: | ||||
|                 tags = "" | ||||
|             return usage, description, tags | ||||
|  | ||||
|         command_keys = " | ".join( | ||||
|             [f"[{self.style}]{self.key}[/{self.style}]"] | ||||
|             + [f"[{self.style}]{alias}[/{self.style}]" for alias in self.aliases] | ||||
|         ) | ||||
|         return ( | ||||
|             Padding( | ||||
|                 Panel( | ||||
|                     f"[{self.style}]{program}[/]{command_keys}  {self.description}", | ||||
|                     expand=False, | ||||
|                 ), | ||||
|                 (0, 2), | ||||
|             ), | ||||
|             f"[{self.style}]{program}[/]{command_keys}", | ||||
|             f"[dim]{self.description}[/dim]", | ||||
|             "", | ||||
|         ) | ||||
|  | ||||
| @@ -384,7 +373,7 @@ class Command(BaseModel): | ||||
|         if self._context: | ||||
|             self._context.log_summary() | ||||
|  | ||||
|     def show_help(self) -> bool: | ||||
|     def render_help(self) -> bool: | ||||
|         """Display the help message for the command.""" | ||||
|         if callable(self.custom_help): | ||||
|             output = self.custom_help() | ||||
|   | ||||
							
								
								
									
										108
									
								
								falyx/falyx.py
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								falyx/falyx.py
									
									
									
									
									
								
							| @@ -41,6 +41,8 @@ from prompt_toolkit.validation import ValidationError | ||||
| from rich import box | ||||
| from rich.console import Console | ||||
| from rich.markdown import Markdown | ||||
| from rich.padding import Padding | ||||
| from rich.panel import Panel | ||||
| from rich.table import Table | ||||
|  | ||||
| from falyx.action.action import Action | ||||
| @@ -347,71 +349,97 @@ class Falyx: | ||||
|     def get_tip(self) -> str: | ||||
|         program = f"{self.program} run " if self.is_cli_mode else "" | ||||
|         tips = [ | ||||
|             f"Tip: Use '{program}?[COMMAND]' to preview a command.", | ||||
|             "Tip: Every command supports aliases—try abbreviating the name!", | ||||
|             f"Tip: Use '{program}H' to reopen this help menu anytime.", | ||||
|             f"Tip: '{program}[COMMAND] --help' prints a detailed help message.", | ||||
|             "Tip: [bold]CLI[/] and [bold]Menu[/] mode—commands run the same way in both.", | ||||
|             f"Tip: Use '{self.program} --never-prompt' to disable all prompts for the [bold italic]entire menu session[/].", | ||||
|             f"Tip: Use '{self.program} --verbose' to enable debug logging for a menu session.", | ||||
|             f"Tip: '{self.program} --debug-hooks' will trace every before/after hook in action.", | ||||
|             f"Tip: Run commands directly from the CLI: '{self.program} run [COMMAND] [OPTIONS]'.", | ||||
|             f"Use '{program}?[COMMAND]' to preview a command.", | ||||
|             "Every command supports aliases—try abbreviating the name!", | ||||
|             f"Use '{program}H' to reopen this help menu anytime.", | ||||
|             f"'{program}[COMMAND] --help' prints a detailed help message.", | ||||
|             "[bold]CLI[/] and [bold]Menu[/] mode—commands run the same way in both.", | ||||
|             f"'{self.program} --never-prompt' to disable all prompts for the [bold italic]entire menu session[/].", | ||||
|             f"Use '{self.program} --verbose' to enable debug logging for a menu session.", | ||||
|             f"'{self.program} --debug-hooks' will trace every before/after hook in action.", | ||||
|             f"Run commands directly from the CLI: '{self.program} run [COMMAND] [OPTIONS]'.", | ||||
|         ] | ||||
|         if self.is_cli_mode: | ||||
|             tips.extend( | ||||
|                 [ | ||||
|                     f"Tip: Use '{self.program} run ?' to list all commands at any time.", | ||||
|                     f"Tip: Use '{self.program} --never-prompt run [COMMAND] [OPTIONS]' to disable all prompts for [bold italic]just this command[/].", | ||||
|                     f"Tip: Use '{self.program} run --skip-confirm [COMMAND] [OPTIONS]' to skip confirmations.", | ||||
|                     f"Tip: Use '{self.program} run --summary [COMMAND] [OPTIONS]' to print a post-run summary.", | ||||
|                     f"Tip: Use '{self.program} --verbose run [COMMAND] [OPTIONS]' to enable debug logging for any run.", | ||||
|                     "Tip: Use '--skip-confirm' for automation scripts where no prompts are wanted.", | ||||
|                     f"Use '{self.program} run ?' to list all commands at any time.", | ||||
|                     f"Use '{self.program} --never-prompt run [COMMAND] [OPTIONS]' to disable all prompts for [bold italic]just this command[/].", | ||||
|                     f"Use '{self.program} run --skip-confirm [COMMAND] [OPTIONS]' to skip confirmations.", | ||||
|                     f"Use '{self.program} run --summary [COMMAND] [OPTIONS]' to print a post-run summary.", | ||||
|                     f"Use '{self.program} --verbose run [COMMAND] [OPTIONS]' to enable debug logging for any run.", | ||||
|                     "Use '--skip-confirm' for automation scripts where no prompts are wanted.", | ||||
|                 ] | ||||
|             ) | ||||
|         else: | ||||
|             tips.extend( | ||||
|                 [ | ||||
|                     "Tip: Use '[?]' alone to list all commands at any time.", | ||||
|                     "Tip: '[CTRL+KEY]' toggles are available in menu mode for quick switches.", | ||||
|                     "Tip: '[Y]' opens the command history viewer.", | ||||
|                     "Tip: Use '[X]' in menu mode to exit.", | ||||
|                     "Use '[?]' alone to list all commands at any time.", | ||||
|                     "'[CTRL+KEY]' toggles are available in menu mode for quick switches.", | ||||
|                     "'[Y]' opens the command history viewer.", | ||||
|                     "Use '[X]' in menu mode to exit.", | ||||
|                 ] | ||||
|             ) | ||||
|         return choice(tips) | ||||
|  | ||||
|     async def _show_help(self, tag: str = "") -> None: | ||||
|         self.console.print("[bold]help:[/bold]") | ||||
|     async def _render_help(self, tag: str = "") -> None: | ||||
|         if tag: | ||||
|             tag_lower = tag.lower() | ||||
|             self.console.print(f"[bold]{tag_lower}:[/bold]") | ||||
|             commands = [ | ||||
|                 command | ||||
|                 for command in self.commands.values() | ||||
|                 if any(tag_lower == tag.lower() for tag in command.tags) | ||||
|             ] | ||||
|             if not commands: | ||||
|                 self.console.print(f"'{tag}'... Nothing to show here") | ||||
|                 return | ||||
|             for command in commands: | ||||
|                 usage, description = command.help_signature | ||||
|                 usage, description, _ = command.help_signature | ||||
|                 self.console.print(usage) | ||||
|                 if description: | ||||
|                     self.console.print(description) | ||||
|             return | ||||
|  | ||||
|         self.console.print("[bold]help:[/bold]") | ||||
|         for command in self.commands.values(): | ||||
|             usage, description = command.help_signature | ||||
|             self.console.print(usage) | ||||
|             if description: | ||||
|                 self.console.print(description) | ||||
|             usage, description, tag = command.help_signature | ||||
|             self.console.print( | ||||
|                 Padding( | ||||
|                     Panel( | ||||
|                         usage, | ||||
|                         expand=False, | ||||
|                         title=description, | ||||
|                         title_align="left", | ||||
|                         subtitle=tag, | ||||
|                     ), | ||||
|                     (0, 2), | ||||
|                 ) | ||||
|             ) | ||||
|         if self.help_command: | ||||
|             usage, description = self.help_command.help_signature | ||||
|             self.console.print(usage) | ||||
|             self.console.print(description) | ||||
|             usage, description, _ = self.help_command.help_signature | ||||
|             self.console.print( | ||||
|                 Padding( | ||||
|                     Panel(usage, expand=False, title=description, title_align="left"), | ||||
|                     (0, 2), | ||||
|                 ) | ||||
|             ) | ||||
|         if not self.is_cli_mode: | ||||
|             if self.history_command: | ||||
|                 usage, description = self.history_command.help_signature | ||||
|                 self.console.print(usage) | ||||
|                 self.console.print(description) | ||||
|             usage, _ = self.exit_command.help_signature | ||||
|             self.console.print(usage) | ||||
|         self.console.print(self.get_tip()) | ||||
|                 usage, description, _ = self.history_command.help_signature | ||||
|                 self.console.print( | ||||
|                     Padding( | ||||
|                         Panel(usage, expand=False, title=description, title_align="left"), | ||||
|                         (0, 2), | ||||
|                     ) | ||||
|                 ) | ||||
|             usage, description, _ = self.exit_command.help_signature | ||||
|             self.console.print( | ||||
|                 Padding( | ||||
|                     Panel(usage, expand=False, title=description, title_align="left"), | ||||
|                     (0, 2), | ||||
|                 ) | ||||
|             ) | ||||
|         self.console.print(f"[bold]tip:[/bold] {self.get_tip()}") | ||||
|  | ||||
|     def _get_help_command(self) -> Command: | ||||
|         """Returns the help command for the menu.""" | ||||
| @@ -434,7 +462,7 @@ class Falyx: | ||||
|             aliases=["?", "HELP", "LIST"], | ||||
|             description="Help", | ||||
|             help_text="Show this help menu", | ||||
|             action=Action("Help", self._show_help), | ||||
|             action=Action("Help", self._render_help), | ||||
|             style=OneColors.LIGHT_YELLOW, | ||||
|             arg_parser=parser, | ||||
|             ignore_in_history=True, | ||||
| @@ -884,7 +912,7 @@ class Falyx: | ||||
|                 args, kwargs = await run_command.parse_args(input_args, from_validate) | ||||
|             except (CommandArgumentError, Exception) as error: | ||||
|                 if not from_validate: | ||||
|                     run_command.show_help() | ||||
|                     run_command.render_help() | ||||
|                     self.console.print( | ||||
|                         f"[{OneColors.DARK_RED}]❌ [{run_command.key}]: {error}" | ||||
|                     ) | ||||
| @@ -955,7 +983,7 @@ class Falyx: | ||||
|     async def process_command(self) -> bool: | ||||
|         """Processes the action of the selected command.""" | ||||
|         app = get_app() | ||||
|         await asyncio.sleep(0.01) | ||||
|         await asyncio.sleep(0.1) | ||||
|         app.invalidate() | ||||
|         with patch_stdout(raw=True): | ||||
|             choice = await self.prompt_session.prompt_async() | ||||
| @@ -1171,7 +1199,7 @@ class Falyx: | ||||
|             self.register_all_with_debug_hooks() | ||||
|  | ||||
|         if self.cli_args.command == "list": | ||||
|             await self._show_help(tag=self.cli_args.tag) | ||||
|             await self._render_help(tag=self.cli_args.tag) | ||||
|             sys.exit(0) | ||||
|  | ||||
|         if self.cli_args.command == "version" or self.cli_args.version: | ||||
| @@ -1210,7 +1238,7 @@ class Falyx: | ||||
|                 sys.exit(0) | ||||
|             except CommandArgumentError as error: | ||||
|                 self.console.print(f"[{OneColors.DARK_RED}]❌ ['{command.key}'] {error}") | ||||
|                 command.show_help() | ||||
|                 command.render_help() | ||||
|                 sys.exit(1) | ||||
|             try: | ||||
|                 await self.run_key(self.cli_args.name, args=args, kwargs=kwargs) | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| Defines reusable lifecycle hooks for Falyx Actions and Commands. | ||||
|  | ||||
| This module includes: | ||||
| - `spinner_before_hook`: Automatically starts a spinner before an action runs. | ||||
| - `spinner_teardown_hook`: Stops and clears the spinner after the action completes. | ||||
| - `ResultReporter`: A success hook that displays a formatted result with duration. | ||||
| - `CircuitBreaker`: A failure-aware hook manager that prevents repeated execution | ||||
|   after a configurable number of failures. | ||||
| @@ -11,6 +13,7 @@ These hooks can be registered on `HookManager` instances via lifecycle stages | ||||
| (`before`, `on_error`, `after`, etc.) to enhance resiliency and observability. | ||||
|  | ||||
| Intended for use with: | ||||
| - Actions that require user feedback during long-running operations. | ||||
| - Retryable or unstable actions | ||||
| - Interactive CLI feedback | ||||
| - Safety checks prior to execution | ||||
| @@ -62,7 +65,7 @@ async def spinner_teardown_hook(context: ExecutionContext): | ||||
|     else: | ||||
|         cmd_name = cmd.key | ||||
|     sm = context.action.options_manager.spinners | ||||
|     sm.remove(cmd_name) | ||||
|     await sm.remove(cmd_name) | ||||
|  | ||||
|  | ||||
| class ResultReporter: | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||
| """ | ||||
| Defines `MenuOption` and `MenuOptionMap`, core components used to construct | ||||
| interactive menus within Falyx Actions such as `MenuAction` and `PromptMenuAction`. | ||||
|   | ||||
| @@ -24,6 +24,7 @@ Public Interface: | ||||
| - `parse_args(...)`: Parse CLI-style argument list into a `dict[str, Any]`. | ||||
| - `parse_args_split(...)`: Return `(*args, **kwargs)` for Action invocation. | ||||
| - `render_help()`: Render a rich-styled help panel. | ||||
| - `render_tldr()`: Render quick usage examples. | ||||
| - `suggest_next(...)`: Return suggested flags or values for completion. | ||||
|  | ||||
| Example Usage: | ||||
| @@ -1379,8 +1380,12 @@ class CommandArgumentParser: | ||||
|             usage = f"{command} {example.usage.strip()}" | ||||
|             description = example.description.strip() | ||||
|             block = f"[bold]{usage}[/bold]" | ||||
|             self.console.print(Padding(Panel(block, expand=False), (0, 2))) | ||||
|             self.console.print(f"    {description}", style="dim") | ||||
|             self.console.print( | ||||
|                 Padding( | ||||
|                     Panel(block, expand=False, title=description, title_align="left"), | ||||
|                     (0, 2), | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|     def __eq__(self, other: object) -> bool: | ||||
|         if not isinstance(other, CommandArgumentParser): | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||
| """ | ||||
| Centralized spinner rendering for Falyx CLI. | ||||
|  | ||||
| @@ -43,13 +44,13 @@ Design Notes: | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import threading | ||||
|  | ||||
| from rich.console import Group | ||||
| from rich.live import Live | ||||
| from rich.spinner import Spinner | ||||
|  | ||||
| from falyx.console import console | ||||
| from falyx.logger import logger | ||||
| from falyx.themes import OneColors | ||||
|  | ||||
|  | ||||
| @@ -133,7 +134,7 @@ class SpinnerManager: | ||||
|         self._task: asyncio.Task | None = None | ||||
|         self._running: bool = False | ||||
|  | ||||
|         self._start_lock = threading.Lock() | ||||
|         self._lock = asyncio.Lock() | ||||
|  | ||||
|     async def add( | ||||
|         self, | ||||
| @@ -150,9 +151,10 @@ class SpinnerManager: | ||||
|             spinner_style=spinner_style, | ||||
|             spinner_speed=spinner_speed, | ||||
|         ) | ||||
|         with self._start_lock: | ||||
|         async with self._lock: | ||||
|             if not self._running: | ||||
|                 self._start_live() | ||||
|                 logger.debug("[%s] Starting spinner manager Live loop.", name) | ||||
|                 await self._start_live() | ||||
|  | ||||
|     def update( | ||||
|         self, | ||||
| @@ -174,13 +176,17 @@ class SpinnerManager: | ||||
|                 data.spinner_type = spinner_type | ||||
|                 data.spinner = Spinner(spinner_type, text=data.text) | ||||
|  | ||||
|     def remove(self, name: str): | ||||
|     async def remove(self, name: str): | ||||
|         """Remove a spinner and stop the Live loop if no spinners remain.""" | ||||
|         self._spinners.pop(name, None) | ||||
|         if not self._spinners: | ||||
|             self._running = False | ||||
|         async with self._lock: | ||||
|             if not self._spinners: | ||||
|                 logger.debug("[%s] Stopping spinner manager, no spinners left.", name) | ||||
|                 if self._task: | ||||
|                     self._task.cancel() | ||||
|                 self._running = False | ||||
|  | ||||
|     def _start_live(self): | ||||
|     async def _start_live(self): | ||||
|         """Start the Live rendering loop in the background.""" | ||||
|         self._running = True | ||||
|         self._task = asyncio.create_task(self._live_loop()) | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed | ||||
| """ | ||||
| colors.py | ||||
|  | ||||
| A Python module that integrates the Nord color palette with the Rich library. | ||||
| It defines a metaclass-based NordColors class allowing dynamic attribute lookups | ||||
| (e.g., NORD12bu -> "#D08770 bold underline") and provides a comprehensive Nord-based | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| __version__ = "0.1.77" | ||||
| __version__ = "0.1.78" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| [tool.poetry] | ||||
| name = "falyx" | ||||
| version = "0.1.77" | ||||
| version = "0.1.78" | ||||
| description = "Reliable and introspectable async CLI action framework." | ||||
| authors = ["Roland Thomas Jr <roland@rtj.dev>"] | ||||
| license = "MIT" | ||||
| @@ -10,7 +10,7 @@ packages = [{ include = "falyx" }] | ||||
| [tool.poetry.dependencies] | ||||
| python = ">=3.10" | ||||
| prompt_toolkit = "^3.0" | ||||
| rich = "^13.0" | ||||
| rich = "^14.0" | ||||
| pydantic = "^2.0" | ||||
| python-json-logger = "^3.3.0" | ||||
| toml = "^0.10" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user