hexastack-cli 0.1.0__tar.gz

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.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexastack-cli
3
+ Version: 0.1.0
4
+ Summary: Hexastack CLI presentation adapter with Typer and Rich
5
+ Author: Richard West
6
+ Author-email: Richard West <dopplereffect.us@gmail.com>
7
+ License-Expression: Apache-2.0
8
+ Requires-Dist: hexastack-core
9
+ Requires-Dist: hexastack-cqrs
10
+ Requires-Dist: rich>=13.8.0
11
+ Requires-Dist: typer>=0.27.1
12
+ Requires-Dist: playwright>=1.49.0 ; extra == 'testing'
13
+ Requires-Python: >=3.13
14
+ Provides-Extra: testing
15
+ Description-Content-Type: text/markdown
16
+
17
+ ![hexastack-cli](../../docs/assets/static/logos/packages/hexastack_cli.png)
18
+
19
+ # hexastack-cli
20
+
21
+ > Typer and Rich presentation adapter for Hexastack: nested commands, aliases, piped outputs, and CQRS dispatching.
22
+
23
+ [![PyPI: hexastack-cli](https://img.shields.io/pypi/v/hexastack-cli.svg)](https://pypi.org/project/hexastack-cli/)
24
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
25
+ [![Coverage](https://codecov.io/github/TheTrueSCU/hexastack/graph/badge.svg?component=hexastack_cli)](https://codecov.io/github/TheTrueSCU/hexastack)
26
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE)
27
+ ---
28
+
29
+ ## 1. Overview & Capabilities
30
+
31
+ `hexastack-cli` turns Hexastack CQRS commands and queries into intuitive, modern CLI applications:
32
+
33
+ - **Nested Command Hierarchies**: Nest subcommands naturally (`app user create`, `app db migrate`) using `@cli_group`.
34
+ - **Command Aliases**: Register multiple aliases for the same action (`app user new == app user create`).
35
+ - **Feature Flag Gating**: Gate CLI commands dynamically with `@feature_flag_command(...)` and `@cli_command(..., feature_flag=...)`.
36
+ - **Rich Formatted & CI-Friendly Output**: Beautiful tables, panels, and spinners for interactive terminals; clean text/JSON streaming for CI/CD pipelines.
37
+ - **Direct CQRS Dispatching**: Declaratively expose domain commands (`@cli_command`) and queries (`@cli_query`) with automatic parameter parsing and validation.
38
+
39
+ ---
40
+
41
+ ## 2. Package Anatomy & Key Components
42
+
43
+ ```
44
+ hexastack_cli/
45
+ ├── domain/ # CliContext, OutputFormat enum
46
+ ├── adapters/ # create_cli_app, Rich presenters, Typer command runners
47
+ └── infra/ # CliBootstrapper (order=30), @cli_command, @cli_query, @cli_group, @feature_flag_command
48
+ ```
49
+
50
+ ### Key Exports
51
+
52
+ | Category | Exports |
53
+ |---|---|
54
+ | **Application Factory** | `create_cli_app`, `CliBootstrapper` (order=30) |
55
+ | **Decorators** | `@cli_command`, `@cli_query`, `@cli_group`, `@feature_flag_command` |
56
+ | **Presenters** | `RichTerminalPresenter`, `ConsolePresenter`, `TablePresenter`, `JsonPresenter` |
57
+ | **Testing & Demo Narration** | `CliNarrator`, `TerminalEvent` |
58
+
59
+ ---
60
+
61
+ ## 3. Monorepo & Sibling Relationships
62
+
63
+ ```mermaid
64
+ graph TD
65
+ subgraph UserInvocation ["CLI Invocations"]
66
+ INV["Terminal Commands & Scripts"]
67
+ end
68
+
69
+ subgraph CliLayer ["hexastack-cli"]
70
+ TYPER["Typer Application (Nested Groups & Aliases)"]
71
+ RICH["Rich Presenters (Tables, Panels, JSON)"]
72
+ SCAN["CLI Decorator Scanner (@cli_command, @cli_query)"]
73
+ end
74
+
75
+ subgraph CQRSExecution ["hexastack-cqrs"]
76
+ CBUS["CommandBusPort"]
77
+ QBUS["QueryBusPort"]
78
+ end
79
+
80
+ subgraph Kernel ["hexastack-core"]
81
+ DI["rodi.Container"]
82
+ end
83
+
84
+ INV --> TYPER
85
+ TYPER --> SCAN
86
+ SCAN -->|dispatches to| CBUS
87
+ SCAN -->|dispatches to| QBUS
88
+ SCAN --> RICH
89
+
90
+ TYPER -. resolves buses from DI .-> DI
91
+ ```
92
+
93
+ ### Explicit Dependencies (Direct)
94
+ - `hexastack-core`: Core kernel, DI container, and ports.
95
+ - `hexastack-cqrs`: `CommandBusPort` and `QueryBusPort` for message dispatching.
96
+ - `typer>=0.27.1`: CLI command parser and shell completion.
97
+ - `rich>=15.0.0`: Terminal formatting, tables, and colors.
98
+
99
+ ### Implied / Behavioral Relationships (DI-Mediated)
100
+ - **CQRS Integration**: Dispatches CLI argument payloads directly into the application's command and query buses.
101
+ - **Umbrella CLI**: Consumed by the `hexastack` umbrella package to power diagnostic commands (`hexastack info`, `hexastack inspect registry`, `hexastack demo ping`).
102
+
103
+ ---
104
+
105
+ ## 4. Installation
106
+
107
+ ```bash
108
+ # Standalone install
109
+ pip install hexastack-cli
110
+
111
+ # Via umbrella package
112
+ pip install "hexastack[cli]"
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 5. Quickstart Example
118
+
119
+ ```python
120
+ from dataclasses import dataclass
121
+ from hexastack_core.infra.bootstrap import bootstrap
122
+ from hexastack_cqrs.domain.query import Query
123
+ from hexastack_cqrs.infra.decorators import query_handler
124
+ from hexastack_cli.infra.decorators import cli_query, cli_group
125
+
126
+
127
+ @dataclass(frozen=True)
128
+ class CheckStatusQuery(Query):
129
+ service_name: str
130
+
131
+
132
+ @query_handler(CheckStatusQuery)
133
+ class CheckStatusHandler:
134
+ def __call__(self, qry: CheckStatusQuery) -> dict:
135
+ return {"service": qry.service_name, "status": "ONLINE"}
136
+
137
+
138
+ # Expose query as a CLI command
139
+ cli_query("status", aliases=["st", "health"], help="Check status of a service")(
140
+ CheckStatusQuery
141
+ )
142
+
143
+ runtime = bootstrap(packages_to_scan=[__name__])
144
+ cli_app = runtime.get("cli_app")
145
+ ```
@@ -0,0 +1,129 @@
1
+ ![hexastack-cli](../../docs/assets/static/logos/packages/hexastack_cli.png)
2
+
3
+ # hexastack-cli
4
+
5
+ > Typer and Rich presentation adapter for Hexastack: nested commands, aliases, piped outputs, and CQRS dispatching.
6
+
7
+ [![PyPI: hexastack-cli](https://img.shields.io/pypi/v/hexastack-cli.svg)](https://pypi.org/project/hexastack-cli/)
8
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
9
+ [![Coverage](https://codecov.io/github/TheTrueSCU/hexastack/graph/badge.svg?component=hexastack_cli)](https://codecov.io/github/TheTrueSCU/hexastack)
10
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE)
11
+ ---
12
+
13
+ ## 1. Overview & Capabilities
14
+
15
+ `hexastack-cli` turns Hexastack CQRS commands and queries into intuitive, modern CLI applications:
16
+
17
+ - **Nested Command Hierarchies**: Nest subcommands naturally (`app user create`, `app db migrate`) using `@cli_group`.
18
+ - **Command Aliases**: Register multiple aliases for the same action (`app user new == app user create`).
19
+ - **Feature Flag Gating**: Gate CLI commands dynamically with `@feature_flag_command(...)` and `@cli_command(..., feature_flag=...)`.
20
+ - **Rich Formatted & CI-Friendly Output**: Beautiful tables, panels, and spinners for interactive terminals; clean text/JSON streaming for CI/CD pipelines.
21
+ - **Direct CQRS Dispatching**: Declaratively expose domain commands (`@cli_command`) and queries (`@cli_query`) with automatic parameter parsing and validation.
22
+
23
+ ---
24
+
25
+ ## 2. Package Anatomy & Key Components
26
+
27
+ ```
28
+ hexastack_cli/
29
+ ├── domain/ # CliContext, OutputFormat enum
30
+ ├── adapters/ # create_cli_app, Rich presenters, Typer command runners
31
+ └── infra/ # CliBootstrapper (order=30), @cli_command, @cli_query, @cli_group, @feature_flag_command
32
+ ```
33
+
34
+ ### Key Exports
35
+
36
+ | Category | Exports |
37
+ |---|---|
38
+ | **Application Factory** | `create_cli_app`, `CliBootstrapper` (order=30) |
39
+ | **Decorators** | `@cli_command`, `@cli_query`, `@cli_group`, `@feature_flag_command` |
40
+ | **Presenters** | `RichTerminalPresenter`, `ConsolePresenter`, `TablePresenter`, `JsonPresenter` |
41
+ | **Testing & Demo Narration** | `CliNarrator`, `TerminalEvent` |
42
+
43
+ ---
44
+
45
+ ## 3. Monorepo & Sibling Relationships
46
+
47
+ ```mermaid
48
+ graph TD
49
+ subgraph UserInvocation ["CLI Invocations"]
50
+ INV["Terminal Commands & Scripts"]
51
+ end
52
+
53
+ subgraph CliLayer ["hexastack-cli"]
54
+ TYPER["Typer Application (Nested Groups & Aliases)"]
55
+ RICH["Rich Presenters (Tables, Panels, JSON)"]
56
+ SCAN["CLI Decorator Scanner (@cli_command, @cli_query)"]
57
+ end
58
+
59
+ subgraph CQRSExecution ["hexastack-cqrs"]
60
+ CBUS["CommandBusPort"]
61
+ QBUS["QueryBusPort"]
62
+ end
63
+
64
+ subgraph Kernel ["hexastack-core"]
65
+ DI["rodi.Container"]
66
+ end
67
+
68
+ INV --> TYPER
69
+ TYPER --> SCAN
70
+ SCAN -->|dispatches to| CBUS
71
+ SCAN -->|dispatches to| QBUS
72
+ SCAN --> RICH
73
+
74
+ TYPER -. resolves buses from DI .-> DI
75
+ ```
76
+
77
+ ### Explicit Dependencies (Direct)
78
+ - `hexastack-core`: Core kernel, DI container, and ports.
79
+ - `hexastack-cqrs`: `CommandBusPort` and `QueryBusPort` for message dispatching.
80
+ - `typer>=0.27.1`: CLI command parser and shell completion.
81
+ - `rich>=15.0.0`: Terminal formatting, tables, and colors.
82
+
83
+ ### Implied / Behavioral Relationships (DI-Mediated)
84
+ - **CQRS Integration**: Dispatches CLI argument payloads directly into the application's command and query buses.
85
+ - **Umbrella CLI**: Consumed by the `hexastack` umbrella package to power diagnostic commands (`hexastack info`, `hexastack inspect registry`, `hexastack demo ping`).
86
+
87
+ ---
88
+
89
+ ## 4. Installation
90
+
91
+ ```bash
92
+ # Standalone install
93
+ pip install hexastack-cli
94
+
95
+ # Via umbrella package
96
+ pip install "hexastack[cli]"
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 5. Quickstart Example
102
+
103
+ ```python
104
+ from dataclasses import dataclass
105
+ from hexastack_core.infra.bootstrap import bootstrap
106
+ from hexastack_cqrs.domain.query import Query
107
+ from hexastack_cqrs.infra.decorators import query_handler
108
+ from hexastack_cli.infra.decorators import cli_query, cli_group
109
+
110
+
111
+ @dataclass(frozen=True)
112
+ class CheckStatusQuery(Query):
113
+ service_name: str
114
+
115
+
116
+ @query_handler(CheckStatusQuery)
117
+ class CheckStatusHandler:
118
+ def __call__(self, qry: CheckStatusQuery) -> dict:
119
+ return {"service": qry.service_name, "status": "ONLINE"}
120
+
121
+
122
+ # Expose query as a CLI command
123
+ cli_query("status", aliases=["st", "health"], help="Check status of a service")(
124
+ CheckStatusQuery
125
+ )
126
+
127
+ runtime = bootstrap(packages_to_scan=[__name__])
128
+ cli_app = runtime.get("cli_app")
129
+ ```
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "hexastack-cli"
3
+ version = "0.1.0"
4
+ description = "Hexastack CLI presentation adapter with Typer and Rich"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ requires-python = ">=3.13"
8
+ dependencies = [
9
+ "hexastack-core",
10
+ "hexastack-cqrs",
11
+ "rich>=13.8.0",
12
+ "typer>=0.27.1",
13
+ ]
14
+
15
+ [[project.authors]]
16
+ name = "Richard West"
17
+ email = "dopplereffect.us@gmail.com"
18
+
19
+ [project.optional-dependencies]
20
+ testing = ["playwright>=1.49.0"]
21
+
22
+ [project.entry-points."hexastack.bootstrappers"]
23
+ cli = "hexastack_cli.infra.bootstrap:CliBootstrapper"
24
+
25
+ [build-system]
26
+ requires = ["uv_build>=0.12.3,<0.13.0"]
27
+ build-backend = "uv_build"
28
+
29
+ [tool.uv.sources.hexastack-core]
30
+ workspace = true
31
+
32
+ [tool.uv.sources.hexastack-cqrs]
33
+ workspace = true
34
+
35
+ [tool.importlinter]
36
+ root_packages = ["hexastack_cli"]
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "hexastack-cli"
3
+ version = "0.1.0"
4
+ description = "Hexastack CLI presentation adapter with Typer and Rich"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ authors = [
8
+ { name = "Richard West", email = "dopplereffect.us@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+ "hexastack-core",
13
+ "hexastack-cqrs",
14
+ "rich>=13.8.0",
15
+ "typer>=0.27.1",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ testing = [
20
+ "playwright>=1.49.0",
21
+ ]
22
+
23
+ [project.entry-points."hexastack.bootstrappers"]
24
+ cli = "hexastack_cli.infra.bootstrap:CliBootstrapper"
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.12.3,<0.13.0"]
28
+ build-backend = "uv_build"
29
+
30
+ [tool.uv.sources]
31
+ hexastack-core = { workspace = true }
32
+ hexastack-cqrs = { workspace = true }
33
+
34
+ [tool.importlinter]
35
+ root_packages = ["hexastack_cli"]
@@ -0,0 +1,6 @@
1
+ from hexastack_cli import adapters, infra
2
+
3
+ __all__ = [
4
+ "adapters",
5
+ "infra",
6
+ ]
@@ -0,0 +1,13 @@
1
+ from hexastack_cli.adapters.app import create_cli_app
2
+ from hexastack_cli.adapters.presenter import RichTerminalPresenter
3
+ from hexastack_cli.adapters.routing import (
4
+ register_cqrs_command,
5
+ register_cqrs_query,
6
+ )
7
+
8
+ __all__ = [
9
+ "create_cli_app",
10
+ "register_cqrs_command",
11
+ "register_cqrs_query",
12
+ "RichTerminalPresenter",
13
+ ]
@@ -0,0 +1,77 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rodi import Container
4
+
5
+ from hexastack_cli.adapters.presenter import RichTerminalPresenter
6
+ from hexastack_cli.infra.config import HexastackCliConfig
7
+ from hexastack_core.domain import Generic
8
+ from hexastack_cqrs.infra.pipeline import ExecutionPipeline
9
+ from hexastack_cqrs.infra.registries.presenter import PresenterRegistry
10
+
11
+ __all__ = [
12
+ "create_cli_app",
13
+ ]
14
+
15
+
16
+ def create_cli_app(
17
+ config: HexastackCliConfig | None = None,
18
+ container: Container | None = None,
19
+ pipeline: ExecutionPipeline | None = None,
20
+ console: Console | None = None,
21
+ ) -> typer.Typer:
22
+ """Factory creating and configuring a Typer CLI application integrated with Hexastack.
23
+
24
+ Notes/Architectural Intent:
25
+ Assembles a Typer CLI instance configured with Rich formatting, binds container
26
+ and pipeline references, supports top-level --version, ensures multi-command dispatching,
27
+ and registers terminal presenters into the presenter registry.
28
+
29
+ Args:
30
+ config: Optional HexastackCliConfig instance.
31
+ container: Optional rodi Container instance.
32
+ pipeline: Optional ExecutionPipeline instance.
33
+ console: Optional rich Console instance.
34
+
35
+ Returns:
36
+ Configured Typer application instance.
37
+
38
+ Raises:
39
+ None.
40
+ """
41
+ cfg = config or HexastackCliConfig()
42
+ active_console = console or Console()
43
+
44
+ app = typer.Typer(
45
+ name=cfg.app_name,
46
+ help=cfg.help_text,
47
+ rich_markup_mode="rich" if cfg.rich_markup else None,
48
+ no_args_is_help=True,
49
+ )
50
+
51
+ def _version_callback(value: bool) -> None:
52
+ if value:
53
+ active_console.print(f"{cfg.app_name} {cfg.version}")
54
+ raise typer.Exit()
55
+
56
+ # Establish root callback to enforce multi-command structure and handle --version
57
+ @app.callback()
58
+ def _main(
59
+ version: bool | None = typer.Option(
60
+ None,
61
+ "--version",
62
+ "-v",
63
+ help="Show the application version and exit.",
64
+ callback=_version_callback,
65
+ is_eager=True,
66
+ ),
67
+ ) -> None:
68
+ pass
69
+
70
+ # Register RichTerminalPresenter if presenter registry is present in DI
71
+ if container is not None and PresenterRegistry in container:
72
+ pres_reg = container.resolve(PresenterRegistry)
73
+ terminal_presenter = RichTerminalPresenter(console=active_console)
74
+ pres_reg.register(Generic, "rich", terminal_presenter)
75
+ container.add_instance(terminal_presenter, declared_class=RichTerminalPresenter)
76
+
77
+ return app
@@ -0,0 +1,137 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+
11
+ from hexastack_core.domain import Generic
12
+ from hexastack_core.ports.presenter import PresenterPort
13
+
14
+
15
+ class RichTerminalPresenter(PresenterPort):
16
+ """Terminal presenter formatting domain models into stylized Rich panels, JSON, or plain text.
17
+
18
+ Notes/Architectural Intent:
19
+ Implements Presenter port for terminal and CI/pipe environments.
20
+ Supports structured JSON, plain line-oriented text, and interactive Rich tables.
21
+ Automatically respects NO_COLOR and non-TTY stdout streams.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ console: Console | None = None,
27
+ stderr_console: Console | None = None,
28
+ ) -> None:
29
+ """Initialize RichTerminalPresenter with optional stdout and stderr Console instances.
30
+
31
+ Args:
32
+ console: Optional rich.console.Console instance for stdout.
33
+ stderr_console: Optional rich.console.Console instance for stderr.
34
+ """
35
+ no_color = bool(os.environ.get("NO_COLOR"))
36
+ self._console = console or Console(no_color=no_color, highlight=not no_color)
37
+ self._stderr = stderr_console or Console(
38
+ stderr=True, no_color=no_color, highlight=not no_color
39
+ )
40
+
41
+ def _present_json(self, data: Any) -> Any:
42
+ """Render raw, pipe-friendly JSON to stdout."""
43
+ json_str = json.dumps(data, indent=2, default=str)
44
+ sys.stdout.write(json_str + "\n")
45
+ sys.stdout.flush()
46
+ return data
47
+
48
+ def _present_plain(self, data: Any) -> Any:
49
+ """Render TSV/newline-delimited text to stdout for Unix pipeline processing."""
50
+ if isinstance(data, dict):
51
+ for k, v in data.items():
52
+ sys.stdout.write(f"{k}\t{v}\n")
53
+ elif isinstance(data, list):
54
+ for item in data:
55
+ sys.stdout.write(f"{item}\n")
56
+ else:
57
+ sys.stdout.write(f"{data}\n")
58
+ sys.stdout.flush()
59
+ return data
60
+
61
+ def _present_table(self, instance: Generic, data: Any) -> Any:
62
+ """Render colorized Rich table/panel to stdout."""
63
+ if isinstance(data, dict):
64
+ table = Table(show_header=True, header_style="bold magenta")
65
+ table.add_column("Field", style="cyan")
66
+ table.add_column("Value", style="green")
67
+ for k, v in data.items():
68
+ val_str = (
69
+ json.dumps(v, indent=2) if isinstance(v, dict | list) else str(v)
70
+ )
71
+ table.add_row(str(k), val_str)
72
+ self._console.print(
73
+ Panel(table, title=type(instance).__name__, border_style="blue")
74
+ )
75
+ elif isinstance(data, list):
76
+ for item in data:
77
+ self._console.print(f"[cyan]•[/cyan] {item}")
78
+ else:
79
+ self._console.print(f"[bold green]{data}[/bold green]")
80
+ return data
81
+
82
+ def present(
83
+ self,
84
+ instance: Generic,
85
+ format_mode: str | None = None,
86
+ ) -> Any:
87
+ """Format and print a domain Generic instance to stdout based on requested format mode.
88
+
89
+ Args:
90
+ instance: Domain Generic or DTO model instance.
91
+ format_mode: Optional format mode ('table', 'json', 'plain').
92
+
93
+ Returns:
94
+ The presented raw data representation.
95
+
96
+ Raises:
97
+ None.
98
+ """
99
+ data = instance.model_dump() if isinstance(instance, BaseModel) else instance
100
+
101
+ mode = (format_mode or "table").lower()
102
+
103
+ if mode == "json":
104
+ return self._present_json(data)
105
+ if mode == "plain":
106
+ return self._present_plain(data)
107
+ return self._present_table(instance, data)
108
+
109
+ def print_error(self, message: str) -> None:
110
+ """Print an error message to stderr.
111
+
112
+ Args:
113
+ message: The error message string.
114
+
115
+ Returns:
116
+ None.
117
+
118
+ Raises:
119
+ None.
120
+ """
121
+ self._stderr.print(f"[bold red]Error:[/bold red] {message}")
122
+
123
+ def print_exception(self) -> None:
124
+ """Print a rich formatted traceback to stderr for debugging.
125
+
126
+ Returns:
127
+ None.
128
+
129
+ Raises:
130
+ None.
131
+ """
132
+ self._stderr.print_exception(show_locals=False)
133
+
134
+
135
+ __all__ = [
136
+ "RichTerminalPresenter",
137
+ ]