From 0c55607efd78b459e15684d3e7ac7641bac1d97f Mon Sep 17 00:00:00 2001 From: MaceWindu Date: Mon, 11 May 2026 10:46:11 +0200 Subject: [PATCH] Add `ensure` subcommand for resilient SessionStart hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ensure` wraps connect → idempotent Windows re-pair → power-on → set colour (default #ffffff), so a SessionStart hook recovers when the strip has fallen out of the paired list or is powered off — both of which silently break a plain `color #ffffff` write. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 2 +- README.md | 7 ++++++- src/Claudelk.Cli/Dispatcher.cs | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d2310fa..a7fadab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ dotnet publish src/Claudelk.Cli -c Release # hooks invoke. ~1.6s cold per command. ``` -CLI subcommands: `scan [--debug]`, `pair `, `on`, `off`, `color <#RRGGBB>`, `blink <#RRGGBB> [pulses] [ms] [--end <#RRGGBB>]`, `brightness 0-100`, `speed 0-100`, `effect 0x80-0x9f`, `temp 0-100`. All commands accept `--device ` to override the saved default. +CLI subcommands: `scan [--debug]`, `pair `, `ensure [--color <#RRGGBB>]` (verify connection; reconnect/re-pair/power-on as needed, then set colour — default `#ffffff`; used by the `SessionStart` hook), `on`, `off`, `color <#RRGGBB>`, `blink <#RRGGBB> [pulses] [ms] [--end <#RRGGBB>]`, `brightness 0-100`, `speed 0-100`, `effect 0x80-0x9f`, `temp 0-100`. All commands accept `--device ` to override the saved default. ## Tests diff --git a/README.md b/README.md index f9c4777..7a7c236 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ claudelk scan # add --debug to see all BLE adverts claudelk pair # 3. Drive it. +claudelk ensure # verify connection (re-pair / power-on if needed), then white claudelk on claudelk color "#ff8800" claudelk blink "#ff0000" # default 4×250ms, holds colour @@ -98,11 +99,15 @@ waiting for your next message. Never turns off. | `Notification` | `permission_prompt` | red `#ff0000`, 5 s blink, ends on white | | `Notification` | `idle_prompt` | yellow `#ffff00`, 3 s blink, ends on white | +`ensure` is used instead of plain `color` so a fallen-out Windows pairing or +a powered-off strip is repaired on session start (reconnect → idempotent +re-pair → power-on → set white). + ```json "SessionStart": [{ "hooks": [{ "type": "command", "shell": "powershell", "async": true, - "command": "& 'C:\\path\\to\\claudelk.exe' color '#ffffff'" + "command": "& 'C:\\path\\to\\claudelk.exe' ensure" }] }], "Notification": [ diff --git a/src/Claudelk.Cli/Dispatcher.cs b/src/Claudelk.Cli/Dispatcher.cs index 7f82bf2..4ccf242 100644 --- a/src/Claudelk.Cli/Dispatcher.cs +++ b/src/Claudelk.Cli/Dispatcher.cs @@ -20,6 +20,7 @@ public static async Task RunAsync(string[] args) { "scan" => await ScanAsync(args), "pair" => await PairAsync(args), + "ensure" => await EnsureAsync(args), "on" => await SimpleCommandAsync(args, d => d.TurnOnAsync()), "off" => await SimpleCommandAsync(args, d => d.TurnOffAsync()), "color" => await ColorAsync(args), @@ -56,6 +57,8 @@ private static void PrintHelp() Commands: scan [--debug] Discover nearby ELK-BLEDOM strips (--debug lists all BLE adverts) pair Save a device as the default target + ensure [--color <#RRGGBB>] Verify connection; reconnect/repair-pair/power-on if needed, then set colour + (default colour: #ffffff) on Power on the saved device off Power off the saved device color <#RRGGBB | R G B> Set RGB color @@ -146,6 +149,29 @@ private static async Task PairAsync(string[] args) return 0; } + private static async Task EnsureAsync(string[] args) + { + var colorHex = ExtractOption(args, "--color") ?? "#ffffff"; + if (!TryParseHex(colorHex, out var r, out var g, out var b)) + { + Console.Error.WriteLine("ensure: --color expects '#RRGGBB'."); + return 1; + } + + using var device = await ResolveDeviceAsync(args); + + // Restore the paired-list fast path if ConnectByIdAsync had to fall back + // to an advertisement scan. PairAsync is a no-op when already paired. + await device.PairWithWindowsAsync(); + + // Power on explicitly: this firmware silently drops SetColor writes that + // arrive while the strip is in the off state. + await device.TurnOnAsync(); + await device.SetColorAsync(r, g, b); + Console.WriteLine($"Ensured {device.Name} ({device.Id}) on, colour #{r:X2}{g:X2}{b:X2}."); + return 0; + } + private static async Task SimpleCommandAsync(string[] args, Func action) { using var device = await ResolveDeviceAsync(args);