falyx 0.1.21__py3-none-any.whl → 0.1.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
falyx/init.py CHANGED
@@ -4,27 +4,85 @@ from pathlib import Path
4
4
  from rich.console import Console
5
5
 
6
6
  TEMPLATE_TASKS = """\
7
- async def build():
8
- print("🔨 Building project...")
9
- return "Build complete!"
7
+ # This file is used by falyx.yaml to define CLI actions.
8
+ # You can run: falyx run [key] or falyx list to see available commands.
10
9
 
11
- async def test():
12
- print("🧪 Running tests...")
13
- return "Tests complete!"
10
+ import asyncio
11
+ import json
12
+
13
+ from falyx.action import Action, ChainedAction
14
+ from falyx.io_action import ShellAction
15
+ from falyx.selection_action import SelectionAction
16
+
17
+
18
+ post_ids = ["1", "2", "3", "4", "5"]
19
+
20
+ pick_post = SelectionAction(
21
+ name="Pick Post ID",
22
+ selections=post_ids,
23
+ title="Choose a Post ID",
24
+ prompt_message="Select a post > ",
25
+ )
26
+
27
+ fetch_post = ShellAction(
28
+ name="Fetch Post via curl",
29
+ command_template="curl https://jsonplaceholder.typicode.com/posts/{}",
30
+ )
31
+
32
+ async def get_post_title(last_result):
33
+ return json.loads(last_result).get("title", "No title found")
34
+
35
+ post_flow = ChainedAction(
36
+ name="Fetch and Parse Post",
37
+ actions=[pick_post, fetch_post, get_post_title],
38
+ auto_inject=True,
39
+ )
40
+
41
+ async def hello():
42
+ print("👋 Hello from Falyx!")
43
+ return "Hello Complete!"
44
+
45
+ async def some_work():
46
+ await asyncio.sleep(2)
47
+ print("Work Finished!")
48
+ return "Work Complete!"
49
+
50
+ work_action = Action(
51
+ name="Work Action",
52
+ action=some_work,
53
+ )
14
54
  """
15
55
 
16
56
  TEMPLATE_CONFIG = """\
17
- - key: B
18
- description: Build the project
19
- action: tasks.build
20
- aliases: [build]
21
- spinner: true
22
-
23
- - key: T
24
- description: Run tests
25
- action: tasks.test
26
- aliases: [test]
27
- spinner: true
57
+ # falyx.yaml — Config-driven CLI definition
58
+ # Define your commands here and point to Python callables in tasks.py
59
+ title: Sample CLI Project
60
+ prompt:
61
+ - ["#61AFEF bold", "FALYX > "]
62
+ columns: 3
63
+ welcome_message: "🚀 Welcome to your new CLI project!"
64
+ exit_message: "👋 See you next time!"
65
+ commands:
66
+ - key: S
67
+ description: Say Hello
68
+ action: tasks.hello
69
+ aliases: [hi, hello]
70
+ tags: [example]
71
+
72
+ - key: P
73
+ description: Get Post Title
74
+ action: tasks.post_flow
75
+ aliases: [submit]
76
+ preview_before_confirm: true
77
+ confirm: true
78
+ tags: [demo, network]
79
+
80
+ - key: G
81
+ description: Do Some Work
82
+ action: tasks.work_action
83
+ aliases: [work]
84
+ spinner: true
85
+ spinner_message: "Working..."
28
86
  """
29
87
 
30
88
  GLOBAL_TEMPLATE_TASKS = """\
@@ -33,10 +91,12 @@ async def cleanup():
33
91
  """
34
92
 
35
93
  GLOBAL_CONFIG = """\
36
- - key: C
37
- description: Cleanup temp files
38
- action: tasks.cleanup
39
- aliases: [clean, cleanup]
94
+ title: Global Falyx Config
95
+ commands:
96
+ - key: C
97
+ description: Cleanup temp files
98
+ action: tasks.cleanup
99
+ aliases: [clean, cleanup]
40
100
  """
41
101
 
42
102
  console = Console(color_system="auto")
@@ -56,7 +116,7 @@ def init_project(name: str = ".") -> None:
56
116
  tasks_path.write_text(TEMPLATE_TASKS)
57
117
  config_path.write_text(TEMPLATE_CONFIG)
58
118
 
59
- print(f"✅ Initialized Falyx project in {target}")
119
+ console.print(f"✅ Initialized Falyx project in {target}")
60
120
 
61
121
 
62
122
  def init_global() -> None:
falyx/io_action.py CHANGED
@@ -16,6 +16,7 @@ Common usage includes shell-like filters, input transformers, or any tool that
16
16
  needs to consume input from another process or pipeline.
17
17
  """
18
18
  import asyncio
19
+ import shlex
19
20
  import subprocess
20
21
  import sys
21
22
  from typing import Any
@@ -59,6 +60,7 @@ class BaseIOAction(BaseAction):
59
60
  def __init__(
60
61
  self,
61
62
  name: str,
63
+ *,
62
64
  hooks: HookManager | None = None,
63
65
  mode: str = "buffered",
64
66
  logging_hooks: bool = True,
@@ -182,13 +184,13 @@ class ShellAction(BaseIOAction):
182
184
 
183
185
  Designed for quick integration with shell tools like `grep`, `ping`, `jq`, etc.
184
186
 
185
- ⚠️ Warning:
186
- Be cautious when using ShellAction with untrusted user input. Since it uses
187
- `shell=True`, unsanitized input can lead to command injection vulnerabilities.
188
- Avoid passing raw user input directly unless the template or use case is secure.
187
+ ⚠️ Security Warning:
188
+ By default, ShellAction uses `shell=True`, which can be dangerous with unsanitized input.
189
+ To mitigate this, set `safe_mode=True` to use `shell=False` with `shlex.split()`.
189
190
 
190
191
  Features:
191
192
  - Automatically handles input parsing (str/bytes)
193
+ - `safe_mode=True` disables shell interpretation and runs with `shell=False`
192
194
  - Captures stdout and stderr from shell execution
193
195
  - Raises on non-zero exit codes with stderr as the error
194
196
  - Result is returned as trimmed stdout string
@@ -198,11 +200,15 @@ class ShellAction(BaseIOAction):
198
200
  name (str): Name of the action.
199
201
  command_template (str): Shell command to execute. Must include `{}` to include input.
200
202
  If no placeholder is present, the input is not included.
203
+ safe_mode (bool): If True, runs with `shell=False` using shlex parsing (default: False).
201
204
  """
202
205
 
203
- def __init__(self, name: str, command_template: str, **kwargs):
206
+ def __init__(
207
+ self, name: str, command_template: str, safe_mode: bool = False, **kwargs
208
+ ):
204
209
  super().__init__(name=name, **kwargs)
205
210
  self.command_template = command_template
211
+ self.safe_mode = safe_mode
206
212
 
207
213
  def from_input(self, raw: str | bytes) -> str:
208
214
  if not isinstance(raw, (str, bytes)):
@@ -214,7 +220,11 @@ class ShellAction(BaseIOAction):
214
220
  async def _run(self, parsed_input: str) -> str:
215
221
  # Replace placeholder in template, or use raw input as full command
216
222
  command = self.command_template.format(parsed_input)
217
- result = subprocess.run(command, shell=True, text=True, capture_output=True)
223
+ if self.safe_mode:
224
+ args = shlex.split(command)
225
+ result = subprocess.run(args, capture_output=True, text=True)
226
+ else:
227
+ result = subprocess.run(command, shell=True, text=True, capture_output=True)
218
228
  if result.returncode != 0:
219
229
  raise RuntimeError(result.stderr.strip())
220
230
  return result.stdout.strip()
@@ -224,14 +234,18 @@ class ShellAction(BaseIOAction):
224
234
 
225
235
  async def preview(self, parent: Tree | None = None):
226
236
  label = [f"[{OneColors.GREEN_b}]⚙ ShellAction[/] '{self.name}'"]
237
+ label.append(f"\n[dim]Template:[/] {self.command_template}")
238
+ label.append(
239
+ f"\n[dim]Safe mode:[/] {'Enabled' if self.safe_mode else 'Disabled'}"
240
+ )
227
241
  if self.inject_last_result:
228
242
  label.append(f" [dim](injects '{self.inject_into}')[/dim]")
229
- if parent:
230
- parent.add("".join(label))
231
- else:
232
- self.console.print(Tree("".join(label)))
243
+ tree = parent.add("".join(label)) if parent else Tree("".join(label))
244
+ if not parent:
245
+ self.console.print(tree)
233
246
 
234
247
  def __str__(self):
235
248
  return (
236
- f"ShellAction(name={self.name!r}, command_template={self.command_template!r})"
249
+ f"ShellAction(name={self.name!r}, command_template={self.command_template!r}, "
250
+ f"safe_mode={self.safe_mode})"
237
251
  )
falyx/parsers.py CHANGED
@@ -36,7 +36,9 @@ def get_arg_parsers(
36
36
  prog: str | None = "falyx",
37
37
  usage: str | None = None,
38
38
  description: str | None = "Falyx CLI - Run structured async command workflows.",
39
- epilog: str | None = None,
39
+ epilog: (
40
+ str | None
41
+ ) = "Tip: Use 'falyx run ?[COMMAND]' to preview any command from the CLI.",
40
42
  parents: Sequence[ArgumentParser] = [],
41
43
  prefix_chars: str = "-",
42
44
  fromfile_prefix_chars: str | None = None,
@@ -79,6 +81,11 @@ def get_arg_parsers(
79
81
 
80
82
  run_parser = subparsers.add_parser("run", help="Run a specific command")
81
83
  run_parser.add_argument("name", help="Key, alias, or description of the command")
84
+ run_parser.add_argument(
85
+ "--summary",
86
+ action="store_true",
87
+ help="Print an execution summary after command completes",
88
+ )
82
89
  run_parser.add_argument(
83
90
  "--retries", type=int, help="Number of retries on failure", default=0
84
91
  )
@@ -111,6 +118,11 @@ def get_arg_parsers(
111
118
  "run-all", help="Run all commands with a given tag"
112
119
  )
113
120
  run_all_parser.add_argument("-t", "--tag", required=True, help="Tag to match")
121
+ run_all_parser.add_argument(
122
+ "--summary",
123
+ action="store_true",
124
+ help="Print a summary after all tagged commands run",
125
+ )
114
126
  run_all_parser.add_argument(
115
127
  "--retries", type=int, help="Number of retries on failure", default=0
116
128
  )
falyx/prompt_utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  from falyx.options_manager import OptionsManager
2
3
 
3
4
 
falyx/protocols.py CHANGED
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  from __future__ import annotations
2
3
 
3
4
  from typing import Any, Protocol
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  from __future__ import annotations
2
3
 
3
4
  import csv
@@ -33,8 +34,30 @@ class FileReturnType(Enum):
33
34
  TOML = "toml"
34
35
  YAML = "yaml"
35
36
  CSV = "csv"
37
+ TSV = "tsv"
36
38
  XML = "xml"
37
39
 
40
+ @classmethod
41
+ def _get_alias(cls, value: str) -> str:
42
+ aliases = {
43
+ "yml": "yaml",
44
+ "txt": "text",
45
+ "file": "path",
46
+ "filepath": "path",
47
+ }
48
+ return aliases.get(value, value)
49
+
50
+ @classmethod
51
+ def _missing_(cls, value: object) -> FileReturnType:
52
+ if isinstance(value, str):
53
+ normalized = value.lower()
54
+ alias = cls._get_alias(normalized)
55
+ for member in cls:
56
+ if member.value == alias:
57
+ return member
58
+ valid = ", ".join(member.value for member in cls)
59
+ raise ValueError(f"Invalid FileReturnType: '{value}'. Must be one of: {valid}")
60
+
38
61
 
39
62
  class SelectFileAction(BaseAction):
40
63
  """
@@ -42,7 +65,7 @@ class SelectFileAction(BaseAction):
42
65
  - file content (as text, JSON, CSV, etc.)
43
66
  - or the file path itself.
44
67
 
45
- Supported formats: text, json, yaml, toml, csv, xml.
68
+ Supported formats: text, json, yaml, toml, csv, tsv, xml.
46
69
 
47
70
  Useful for:
48
71
  - dynamically loading config files
@@ -72,7 +95,7 @@ class SelectFileAction(BaseAction):
72
95
  prompt_message: str = "Choose > ",
73
96
  style: str = OneColors.WHITE,
74
97
  suffix_filter: str | None = None,
75
- return_type: FileReturnType = FileReturnType.PATH,
98
+ return_type: FileReturnType | str = FileReturnType.PATH,
76
99
  console: Console | None = None,
77
100
  prompt_session: PromptSession | None = None,
78
101
  ):
@@ -83,9 +106,14 @@ class SelectFileAction(BaseAction):
83
106
  self.prompt_message = prompt_message
84
107
  self.suffix_filter = suffix_filter
85
108
  self.style = style
86
- self.return_type = return_type
87
109
  self.console = console or Console(color_system="auto")
88
110
  self.prompt_session = prompt_session or PromptSession()
111
+ self.return_type = self._coerce_return_type(return_type)
112
+
113
+ def _coerce_return_type(self, return_type: FileReturnType | str) -> FileReturnType:
114
+ if isinstance(return_type, FileReturnType):
115
+ return return_type
116
+ return FileReturnType(return_type)
89
117
 
90
118
  def get_options(self, files: list[Path]) -> dict[str, SelectionOption]:
91
119
  value: Any
@@ -106,6 +134,10 @@ class SelectFileAction(BaseAction):
106
134
  with open(file, newline="", encoding="UTF-8") as csvfile:
107
135
  reader = csv.reader(csvfile)
108
136
  value = list(reader)
137
+ elif self.return_type == FileReturnType.TSV:
138
+ with open(file, newline="", encoding="UTF-8") as tsvfile:
139
+ reader = csv.reader(tsvfile, delimiter="\t")
140
+ value = list(reader)
109
141
  elif self.return_type == FileReturnType.XML:
110
142
  tree = ET.parse(file, parser=ET.XMLParser(encoding="UTF-8"))
111
143
  root = tree.getroot()
@@ -137,7 +169,9 @@ class SelectFileAction(BaseAction):
137
169
 
138
170
  options = self.get_options(files)
139
171
 
140
- table = render_selection_dict_table(self.title, options, self.columns)
172
+ table = render_selection_dict_table(
173
+ title=self.title, selections=options, columns=self.columns
174
+ )
141
175
 
142
176
  key = await prompt_for_selection(
143
177
  options.keys(),
@@ -181,7 +215,7 @@ class SelectFileAction(BaseAction):
181
215
  if len(files) > 10:
182
216
  file_list.add(f"[dim]... ({len(files) - 10} more)[/]")
183
217
  except Exception as error:
184
- tree.add(f"[bold red]⚠️ Error scanning directory: {error}[/]")
218
+ tree.add(f"[{OneColors.DARK_RED_b}]⚠️ Error scanning directory: {error}[/]")
185
219
 
186
220
  if not parent:
187
221
  self.console.print(tree)
falyx/selection.py CHANGED
@@ -31,6 +31,7 @@ class SelectionOption:
31
31
 
32
32
  def render_table_base(
33
33
  title: str,
34
+ *,
34
35
  caption: str = "",
35
36
  columns: int = 4,
36
37
  box_style: box.Box = box.SIMPLE,
@@ -71,6 +72,7 @@ def render_table_base(
71
72
  def render_selection_grid(
72
73
  title: str,
73
74
  selections: Sequence[str],
75
+ *,
74
76
  columns: int = 4,
75
77
  caption: str = "",
76
78
  box_style: box.Box = box.SIMPLE,
@@ -86,19 +88,19 @@ def render_selection_grid(
86
88
  ) -> Table:
87
89
  """Create a selection table with the given parameters."""
88
90
  table = render_table_base(
89
- title,
90
- caption,
91
- columns,
92
- box_style,
93
- show_lines,
94
- show_header,
95
- show_footer,
96
- style,
97
- header_style,
98
- footer_style,
99
- title_style,
100
- caption_style,
101
- highlight,
91
+ title=title,
92
+ caption=caption,
93
+ columns=columns,
94
+ box_style=box_style,
95
+ show_lines=show_lines,
96
+ show_header=show_header,
97
+ show_footer=show_footer,
98
+ style=style,
99
+ header_style=header_style,
100
+ footer_style=footer_style,
101
+ title_style=title_style,
102
+ caption_style=caption_style,
103
+ highlight=highlight,
102
104
  )
103
105
 
104
106
  for chunk in chunks(selections, columns):
@@ -110,6 +112,7 @@ def render_selection_grid(
110
112
  def render_selection_indexed_table(
111
113
  title: str,
112
114
  selections: Sequence[str],
115
+ *,
113
116
  columns: int = 4,
114
117
  caption: str = "",
115
118
  box_style: box.Box = box.SIMPLE,
@@ -126,19 +129,19 @@ def render_selection_indexed_table(
126
129
  ) -> Table:
127
130
  """Create a selection table with the given parameters."""
128
131
  table = render_table_base(
129
- title,
130
- caption,
131
- columns,
132
- box_style,
133
- show_lines,
134
- show_header,
135
- show_footer,
136
- style,
137
- header_style,
138
- footer_style,
139
- title_style,
140
- caption_style,
141
- highlight,
132
+ title=title,
133
+ caption=caption,
134
+ columns=columns,
135
+ box_style=box_style,
136
+ show_lines=show_lines,
137
+ show_header=show_header,
138
+ show_footer=show_footer,
139
+ style=style,
140
+ header_style=header_style,
141
+ footer_style=footer_style,
142
+ title_style=title_style,
143
+ caption_style=caption_style,
144
+ highlight=highlight,
142
145
  )
143
146
 
144
147
  for indexes, chunk in zip(
@@ -156,6 +159,7 @@ def render_selection_indexed_table(
156
159
  def render_selection_dict_table(
157
160
  title: str,
158
161
  selections: dict[str, SelectionOption],
162
+ *,
159
163
  columns: int = 2,
160
164
  caption: str = "",
161
165
  box_style: box.Box = box.SIMPLE,
@@ -171,19 +175,19 @@ def render_selection_dict_table(
171
175
  ) -> Table:
172
176
  """Create a selection table with the given parameters."""
173
177
  table = render_table_base(
174
- title,
175
- caption,
176
- columns,
177
- box_style,
178
- show_lines,
179
- show_header,
180
- show_footer,
181
- style,
182
- header_style,
183
- footer_style,
184
- title_style,
185
- caption_style,
186
- highlight,
178
+ title=title,
179
+ caption=caption,
180
+ columns=columns,
181
+ box_style=box_style,
182
+ show_lines=show_lines,
183
+ show_header=show_header,
184
+ show_footer=show_footer,
185
+ style=style,
186
+ header_style=header_style,
187
+ footer_style=footer_style,
188
+ title_style=title_style,
189
+ caption_style=caption_style,
190
+ highlight=highlight,
187
191
  )
188
192
 
189
193
  for chunk in chunks(selections.items(), columns):
@@ -200,6 +204,7 @@ def render_selection_dict_table(
200
204
  async def prompt_for_index(
201
205
  max_index: int,
202
206
  table: Table,
207
+ *,
203
208
  min_index: int = 0,
204
209
  default_selection: str = "",
205
210
  console: Console | None = None,
@@ -211,7 +216,7 @@ async def prompt_for_index(
211
216
  console = console or Console(color_system="auto")
212
217
 
213
218
  if show_table:
214
- console.print(table)
219
+ console.print(table, justify="center")
215
220
 
216
221
  selection = await prompt_session.prompt_async(
217
222
  message=prompt_message,
@@ -224,6 +229,7 @@ async def prompt_for_index(
224
229
  async def prompt_for_selection(
225
230
  keys: Sequence[str] | KeysView[str],
226
231
  table: Table,
232
+ *,
227
233
  default_selection: str = "",
228
234
  console: Console | None = None,
229
235
  prompt_session: PromptSession | None = None,
@@ -249,6 +255,7 @@ async def prompt_for_selection(
249
255
  async def select_value_from_list(
250
256
  title: str,
251
257
  selections: Sequence[str],
258
+ *,
252
259
  console: Console | None = None,
253
260
  prompt_session: PromptSession | None = None,
254
261
  prompt_message: str = "Select an option > ",
@@ -268,20 +275,20 @@ async def select_value_from_list(
268
275
  ):
269
276
  """Prompt for a selection. Return the selected item."""
270
277
  table = render_selection_indexed_table(
271
- title,
272
- selections,
273
- columns,
274
- caption,
275
- box_style,
276
- show_lines,
277
- show_header,
278
- show_footer,
279
- style,
280
- header_style,
281
- footer_style,
282
- title_style,
283
- caption_style,
284
- highlight,
278
+ title=title,
279
+ selections=selections,
280
+ columns=columns,
281
+ caption=caption,
282
+ box_style=box_style,
283
+ show_lines=show_lines,
284
+ show_header=show_header,
285
+ show_footer=show_footer,
286
+ style=style,
287
+ header_style=header_style,
288
+ footer_style=footer_style,
289
+ title_style=title_style,
290
+ caption_style=caption_style,
291
+ highlight=highlight,
285
292
  )
286
293
  prompt_session = prompt_session or PromptSession()
287
294
  console = console or Console(color_system="auto")
@@ -301,6 +308,7 @@ async def select_value_from_list(
301
308
  async def select_key_from_dict(
302
309
  selections: dict[str, SelectionOption],
303
310
  table: Table,
311
+ *,
304
312
  console: Console | None = None,
305
313
  prompt_session: PromptSession | None = None,
306
314
  prompt_message: str = "Select an option > ",
@@ -310,7 +318,7 @@ async def select_key_from_dict(
310
318
  prompt_session = prompt_session or PromptSession()
311
319
  console = console or Console(color_system="auto")
312
320
 
313
- console.print(table)
321
+ console.print(table, justify="center")
314
322
 
315
323
  return await prompt_for_selection(
316
324
  selections.keys(),
@@ -325,6 +333,7 @@ async def select_key_from_dict(
325
333
  async def select_value_from_dict(
326
334
  selections: dict[str, SelectionOption],
327
335
  table: Table,
336
+ *,
328
337
  console: Console | None = None,
329
338
  prompt_session: PromptSession | None = None,
330
339
  prompt_message: str = "Select an option > ",
@@ -334,7 +343,7 @@ async def select_value_from_dict(
334
343
  prompt_session = prompt_session or PromptSession()
335
344
  console = console or Console(color_system="auto")
336
345
 
337
- console.print(table)
346
+ console.print(table, justify="center")
338
347
 
339
348
  selection_key = await prompt_for_selection(
340
349
  selections.keys(),
@@ -351,6 +360,7 @@ async def select_value_from_dict(
351
360
  async def get_selection_from_dict_menu(
352
361
  title: str,
353
362
  selections: dict[str, SelectionOption],
363
+ *,
354
364
  console: Console | None = None,
355
365
  prompt_session: PromptSession | None = None,
356
366
  prompt_message: str = "Select an option > ",
@@ -363,10 +373,10 @@ async def get_selection_from_dict_menu(
363
373
  )
364
374
 
365
375
  return await select_value_from_dict(
366
- selections,
367
- table,
368
- console,
369
- prompt_session,
370
- prompt_message,
371
- default_selection,
376
+ selections=selections,
377
+ table=table,
378
+ console=console,
379
+ prompt_session=prompt_session,
380
+ prompt_message=prompt_message,
381
+ default_selection=default_selection,
372
382
  )
falyx/selection_action.py CHANGED
@@ -1,6 +1,5 @@
1
1
  # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
2
2
  """selection_action.py"""
3
- from pathlib import Path
4
3
  from typing import Any
5
4
 
6
5
  from prompt_toolkit import PromptSession
@@ -29,7 +28,7 @@ class SelectionAction(BaseAction):
29
28
  selections: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption],
30
29
  *,
31
30
  title: str = "Select an option",
32
- columns: int = 2,
31
+ columns: int = 5,
33
32
  prompt_message: str = "Select > ",
34
33
  default_selection: str = "",
35
34
  inject_last_result: bool = False,
@@ -117,7 +116,9 @@ class SelectionAction(BaseAction):
117
116
  await self.hooks.trigger(HookType.BEFORE, context)
118
117
  if isinstance(self.selections, list):
119
118
  table = render_selection_indexed_table(
120
- self.title, self.selections, self.columns
119
+ title=self.title,
120
+ selections=self.selections,
121
+ columns=self.columns,
121
122
  )
122
123
  if not self.never_prompt:
123
124
  index = await prompt_for_index(
@@ -134,7 +135,7 @@ class SelectionAction(BaseAction):
134
135
  result = self.selections[int(index)]
135
136
  elif isinstance(self.selections, dict):
136
137
  table = render_selection_dict_table(
137
- self.title, self.selections, self.columns
138
+ title=self.title, selections=self.selections, columns=self.columns
138
139
  )
139
140
  if not self.never_prompt:
140
141
  key = await prompt_for_selection(
@@ -185,7 +186,7 @@ class SelectionAction(BaseAction):
185
186
  if len(self.selections) > 10:
186
187
  sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]")
187
188
  else:
188
- tree.add("[bold red]Invalid selections type[/]")
189
+ tree.add(f"[{OneColors.DARK_RED_b}]Invalid selections type[/]")
189
190
  return
190
191
 
191
192
  tree.add(f"[dim]Default:[/] '{self.default_selection or self.last_result}'")
falyx/signal_action.py CHANGED
@@ -1,3 +1,4 @@
1
+ # Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
1
2
  from falyx.action import Action
2
3
  from falyx.signals import FlowSignal
3
4
 
falyx/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.21"
1
+ __version__ = "0.1.23"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: falyx
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Reliable and introspectable async CLI action framework.
5
5
  License: MIT
6
6
  Author: Roland Thomas Jr