feat(core): centralize command execution and add standalone command runner

- add CommandExecutor to unify shared command execution lifecycle
  across Falyx and standalone command execution
- add CommandRunner for running a single Command directly as a CLI
  or programmatic entrypoint
- add Command.build() factory and rename parse_args() to resolve_args()
  to clarify the parsing-to-execution boundary
- introduce ExecutionOption and wire execution-scoped flags into
  CommandArgumentParser and Command construction
- refactor Falyx to use FalyxParser/ParseResult and CommandExecutor
  instead of the older argparse-based flow and run_key path
- simplify __main__.py bootstrap by building a bootstrap Falyx instance
  directly and running flx.run()
- improve completer support for preview commands and unique-prefix
  command resolution
- default BottomBar toggle namespace to "default"
- expand module/class docstrings to reflect the new execution architecture
This commit is contained in:
2026-04-07 18:58:24 -04:00
parent 8ce0ffa18e
commit 5d8f3aa603
34 changed files with 3043 additions and 1419 deletions

View File

@@ -71,22 +71,28 @@ async def test_action_with_nargs_positional():
return int(a) * int(b)
action = Action("multiply", multiply)
parser.add_argument("mul", action=ArgumentAction.ACTION, resolver=action, nargs=2)
parser.add_argument(
"mul",
action=ArgumentAction.ACTION,
resolver=action,
nargs=2,
type=int,
)
args = await parser.parse_args(["3", "4"])
assert args["mul"] == 12
with pytest.raises(CommandArgumentError):
await parser.parse_args(["3"])
with pytest.raises(CommandArgumentError):
await parser.parse_args([])
with pytest.raises(CommandArgumentError):
await parser.parse_args(["3", "4", "5"])
with pytest.raises(CommandArgumentError):
await parser.parse_args(["--mul", "3", "4"])
with pytest.raises(CommandArgumentError):
await parser.parse_args([])
@pytest.mark.asyncio
async def test_action_with_nargs_positional_int():
@@ -102,6 +108,9 @@ async def test_action_with_nargs_positional_int():
args = await parser.parse_args(["3", "4"])
assert args["mul"] == 12
with pytest.raises(CommandArgumentError):
await parser.parse_args([])
with pytest.raises(CommandArgumentError):
await parser.parse_args(["3"])
@@ -209,11 +218,19 @@ async def test_action_with_default_and_value_not():
@pytest.mark.asyncio
async def test_action_with_default_and_value_positional():
parser = CommandArgumentParser()
action = Action("default", lambda: "default_value")
parser.add_argument("default", action=ArgumentAction.ACTION, resolver=action)
action = Action("action", lambda x: x)
parser.add_argument(
"default",
action=ArgumentAction.ACTION,
resolver=action,
default="default_value",
)
args = await parser.parse_args([])
assert args["default"] == "default_value"
args = await parser.parse_args(["be"])
assert args["default"] == "be"
with pytest.raises(CommandArgumentError):
await parser.parse_args([])
with pytest.raises(CommandArgumentError):
await parser.parse_args(["be"])
await parser.parse_args(["one", "new_value"])