From 00784ade0317ac5b645d1001d1d7095e5c23f09c Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Mon, 16 Feb 2026 12:12:00 -0800 Subject: [PATCH 1/4] Checkpoint from Copilot CLI for coding agent session --- src/nwp500/cli/__main__.py | 7 ++- src/nwp500/cli/handlers.py | 10 +++- src/nwp500/cli/rich_output.py | 87 +++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/nwp500/cli/__main__.py b/src/nwp500/cli/__main__.py index 741285b..e46a5ba 100644 --- a/src/nwp500/cli/__main__.py +++ b/src/nwp500/cli/__main__.py @@ -294,10 +294,13 @@ def reservations() -> None: @reservations.command("get") # type: ignore[attr-defined] +@click.option("--json", "output_json", is_flag=True, help="Output raw JSON") @async_command -async def reservations_get(mqtt: NavienMqttClient, device: Any) -> None: +async def reservations_get( + mqtt: NavienMqttClient, device: Any, output_json: bool = False +) -> None: """Get current reservation schedule.""" - await handlers.handle_get_reservations_request(mqtt, device) + await handlers.handle_get_reservations_request(mqtt, device, output_json) @reservations.command("set") # type: ignore[attr-defined] diff --git a/src/nwp500/cli/handlers.py b/src/nwp500/cli/handlers.py index 0920912..2f93631 100644 --- a/src/nwp500/cli/handlers.py +++ b/src/nwp500/cli/handlers.py @@ -265,7 +265,7 @@ async def handle_power_request( async def handle_get_reservations_request( - mqtt: NavienMqttClient, device: Device + mqtt: NavienMqttClient, device: Device, output_json: bool = False ) -> None: """Request current reservation schedule.""" future = asyncio.get_running_loop().create_future() @@ -301,7 +301,13 @@ def raw_callback(topic: str, message: dict[str, Any]) -> None: for i, e in enumerate(reservations) ], } - print_json(output) + + if output_json: + print_json(output) + else: + _formatter.print_reservations_table( + output["reservations"], output["reservationEnabled"] + ) future.set_result(None) device_type = str(device.device_info.device_type) diff --git a/src/nwp500/cli/rich_output.py b/src/nwp500/cli/rich_output.py index 13f13b1..3404c20 100644 --- a/src/nwp500/cli/rich_output.py +++ b/src/nwp500/cli/rich_output.py @@ -311,6 +311,20 @@ def print_tou_schedule( decode_price_fn, ) + def print_reservations_table( + self, reservations: list[dict[str, Any]], enabled: bool = False + ) -> None: + """Print reservations as a formatted table. + + Args: + reservations: List of reservation dictionaries + enabled: Whether reservations are enabled globally + """ + if not self.use_rich: + self._print_reservations_plain(reservations, enabled) + else: + self._print_reservations_rich(reservations, enabled) + # Plain text implementations (fallback) def _print_status_plain(self, items: list[tuple[str, str, str]]) -> None: @@ -407,6 +421,38 @@ def _print_tou_plain( f" {p_min:>10.5f} {p_max:>10.5f}" ) + def _print_reservations_plain( + self, reservations: list[dict[str, Any]], enabled: bool = False + ) -> None: + """Plain text reservations output (fallback).""" + status_str = "ENABLED" if enabled else "DISABLED" + print(f"Reservations: {status_str}") + print() + + if not reservations: + print("No reservations configured") + return + + print("RESERVATIONS") + print("=" * 80) + print( + f" {'#':<3} {'Enabled':<10} {'Days':<25} " + f"{'Time':<8} {'Temp (°F)':<10}" + ) + print("=" * 80) + + for res in reservations: + num = res.get("number", "?") + enabled = "Yes" if res.get("enabled", False) else "No" + days_str = _abbreviate_days(res.get("days", [])) + time_str = res.get("time", "??:??") + temp = res.get("temperatureF", "?") + print( + f" {num:<3} {enabled:<10} {days_str:<25} " + f"{time_str:<8} {temp:<10}" + ) + print("=" * 80) + def _print_error_plain( self, message: str, @@ -549,6 +595,47 @@ def _print_tou_rich( self.console.print(table) + def _print_reservations_rich( + self, reservations: list[dict[str, Any]], enabled: bool = False + ) -> None: + """Rich-enhanced reservations output.""" + assert self.console is not None + assert _rich_available + + status_color = "green" if enabled else "red" + status_text = "ENABLED" if enabled else "DISABLED" + panel = cast(Any, Panel)( + f"[{status_color}]{status_text}[/{status_color}]", + title="📋 Reservations Status", + border_style=status_color, + ) + self.console.print(panel) + + if not reservations: + panel = cast(Any, Panel)("No reservations configured") + self.console.print(panel) + return + + table = cast(Any, Table)( + title="💧 Reservations", show_header=True, highlight=True + ) + table.add_column("#", style="cyan", width=3, justify="center") + table.add_column("Status", style="magenta", width=10) + table.add_column("Days", style="white", width=25) + table.add_column("Time", style="yellow", width=8, justify="center") + table.add_column("Temp (°F)", style="green", width=10, justify="center") + + for res in reservations: + num = str(res.get("number", "?")) + enabled = res.get("enabled", False) + status = "[green]✓[/green]" if enabled else "[dim]✗[/dim]" + days_str = _abbreviate_days(res.get("days", [])) + time_str = res.get("time", "??:??") + temp = str(res.get("temperatureF", "?")) + table.add_row(num, status, days_str, time_str, temp) + + self.console.print(table) + # Rich implementations def _print_status_rich(self, items: list[tuple[str, str, str]]) -> None: From f625c6742dfc4baf57377cb81293cf6533adcae1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:16:44 +0000 Subject: [PATCH 2/4] Update CLI documentation for reservations table format - Document new table output as default format - Document --json flag for JSON output - Add examples showing both table and JSON formats - Show global reservation status indicator in example - Update Example 8 to demonstrate both get formats Co-authored-by: eman <19387+eman@users.noreply.github.com> --- docs/python_api/cli.rst | 56 +++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/docs/python_api/cli.rst b/docs/python_api/cli.rst index eda83ac..b0c375b 100644 --- a/docs/python_api/cli.rst +++ b/docs/python_api/cli.rst @@ -375,9 +375,12 @@ View and update reservation schedule. .. code-block:: bash - # Get current reservations + # Get current reservations (table format) python3 -m nwp500.cli reservations get + # Get current reservations (JSON format) + python3 -m nwp500.cli reservations get --json + # Set reservations from JSON python3 -m nwp500.cli reservations set '[{"hour": 6, "min": 0, ...}]' @@ -385,18 +388,40 @@ View and update reservation schedule. .. code-block:: bash - python3 -m nwp500.cli reservations get + python3 -m nwp500.cli reservations get [--json] python3 -m nwp500.cli reservations set [--disabled] -**Options:** +**Options (get):** + +.. option:: --json + + Output raw JSON instead of formatted table. + +**Options (set):** .. option:: --disabled Create reservation in disabled state. -**Output (get):** Current reservation schedule configuration. +**Output (get):** Current reservation schedule displayed as a formatted table by default, +showing the global reservation status (ENABLED/DISABLED) followed by individual reservations. +Use ``--json`` flag for raw JSON output. -**Example Output:** +**Example Table Output:** + +.. code-block:: text + + Reservations: ENABLED + + RESERVATIONS + ================================================================================ + # Enabled Days Time Temp (°F) + ================================================================================ + 1 Yes Mon-Fri 06:00 160 + 2 No Sat-Sun 08:00 140 + ================================================================================ + +**Example JSON Output (--json):** .. code-block:: json @@ -407,10 +432,20 @@ View and update reservation schedule. { "number": 1, "enabled": true, - "days": [1, 1, 1, 1, 1, 0, 0], + "days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "time": "06:00", "mode": 3, - "temperatureF": 140 + "temperatureF": 160, + "raw": {...} + }, + { + "number": 2, + "enabled": false, + "days": ["Saturday", "Sunday"], + "time": "08:00", + "mode": 3, + "temperatureF": 140, + "raw": {...} } ] } @@ -643,8 +678,13 @@ Example 8: Smart Scheduling with Reservations .. code-block:: bash #!/bin/bash - # Set reservation schedule: 6 AM - 10 PM at 140°F on weekdays + # View current reservations (table format - default) + python3 -m nwp500.cli reservations get + # View current reservations (JSON format) + python3 -m nwp500.cli reservations get --json + + # Set reservation schedule: 6 AM - 10 PM at 140°F on weekdays python3 -m nwp500.cli reservations set \ '[{"hour": 6, "min": 0, "mode": 3, "temp": 140, "days": [1,1,1,1,1,0,0]}]' From 3498652c525c2a4b89f704bcae9a3c13bb4c11e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:17:11 +0000 Subject: [PATCH 3/4] Update CHANGELOG for reservations table feature Co-authored-by: eman <19387+eman@users.noreply.github.com> --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9dc9761..e4fe59f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added - **CLI ``tou rates``**: Browse utilities and rate plans for a zip code (``nwp500 tou rates 94903``). - **CLI ``tou plan``**: View converted rate plan details with decoded pricing (``nwp500 tou plan 94903 "EV Rate A"``). - **CLI ``tou apply``**: Apply a rate plan to the water heater with optional ``--enable`` flag to activate TOU via MQTT. +- **CLI Reservations Table Output**: ``nwp-cli reservations get`` now displays reservations as a formatted table by default with global status indicator (ENABLED/DISABLED). Use ``--json`` flag for JSON output. Changed ------- From c8065d50366e5973b77594576d973212120f99fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:33:20 +0000 Subject: [PATCH 4/4] Fix pyright type checking errors in CLI reservations code - handlers.py: Extract dict values to typed variables before passing to formatter - rich_output.py: Avoid reusing 'enabled' parameter name for string value Fixes CI type checking failures (2 errors reduced to 0) Co-authored-by: eman <19387+eman@users.noreply.github.com> --- src/nwp500/cli/handlers.py | 4 +++- src/nwp500/cli/rich_output.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nwp500/cli/handlers.py b/src/nwp500/cli/handlers.py index 2f93631..a5e5f17 100644 --- a/src/nwp500/cli/handlers.py +++ b/src/nwp500/cli/handlers.py @@ -305,8 +305,10 @@ def raw_callback(topic: str, message: dict[str, Any]) -> None: if output_json: print_json(output) else: + reservation_list = output["reservations"] + is_enabled = bool(output["reservationEnabled"]) _formatter.print_reservations_table( - output["reservations"], output["reservationEnabled"] + reservation_list, is_enabled ) future.set_result(None) diff --git a/src/nwp500/cli/rich_output.py b/src/nwp500/cli/rich_output.py index 3404c20..c984485 100644 --- a/src/nwp500/cli/rich_output.py +++ b/src/nwp500/cli/rich_output.py @@ -443,12 +443,13 @@ def _print_reservations_plain( for res in reservations: num = res.get("number", "?") - enabled = "Yes" if res.get("enabled", False) else "No" + is_enabled = res.get("enabled", False) + enabled_str = "Yes" if is_enabled else "No" days_str = _abbreviate_days(res.get("days", [])) time_str = res.get("time", "??:??") temp = res.get("temperatureF", "?") print( - f" {num:<3} {enabled:<10} {days_str:<25} " + f" {num:<3} {enabled_str:<10} {days_str:<25} " f"{time_str:<8} {temp:<10}" ) print("=" * 80)