Skip to content

Commit a804053

Browse files
MaceWinduclaude
andauthored
Add ensure subcommand for resilient SessionStart hook (#2)
`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) <noreply@anthropic.com>
1 parent a00680e commit a804053

3 files changed

Lines changed: 33 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dotnet publish src/Claudelk.Cli -c Release
2020
# hooks invoke. ~1.6s cold per command.
2121
```
2222

23-
CLI subcommands: `scan [--debug]`, `pair <id>`, `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 <id>` to override the saved default.
23+
CLI subcommands: `scan [--debug]`, `pair <id>`, `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 <id>` to override the saved default.
2424

2525
## Tests
2626

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ claudelk scan # add --debug to see all BLE adverts
6464
claudelk pair <device-id>
6565
6666
# 3. Drive it.
67+
claudelk ensure # verify connection (re-pair / power-on if needed), then white
6768
claudelk on
6869
claudelk color "#ff8800"
6970
claudelk blink "#ff0000" # default 4×250ms, holds colour
@@ -98,11 +99,15 @@ waiting for your next message. Never turns off.
9899
| `Notification` | `permission_prompt` | red `#ff0000`, 5 s blink, ends on white |
99100
| `Notification` | `idle_prompt` | yellow `#ffff00`, 3 s blink, ends on white |
100101

102+
`ensure` is used instead of plain `color` so a fallen-out Windows pairing or
103+
a powered-off strip is repaired on session start (reconnect → idempotent
104+
re-pair → power-on → set white).
105+
101106
```json
102107
"SessionStart": [{
103108
"hooks": [{
104109
"type": "command", "shell": "powershell", "async": true,
105-
"command": "& 'C:\\path\\to\\claudelk.exe' color '#ffffff'"
110+
"command": "& 'C:\\path\\to\\claudelk.exe' ensure"
106111
}]
107112
}],
108113
"Notification": [

src/Claudelk.Cli/Dispatcher.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public static async Task<int> RunAsync(string[] args)
2020
{
2121
"scan" => await ScanAsync(args),
2222
"pair" => await PairAsync(args),
23+
"ensure" => await EnsureAsync(args),
2324
"on" => await SimpleCommandAsync(args, d => d.TurnOnAsync()),
2425
"off" => await SimpleCommandAsync(args, d => d.TurnOffAsync()),
2526
"color" => await ColorAsync(args),
@@ -56,6 +57,8 @@ private static void PrintHelp()
5657
Commands:
5758
scan [--debug] Discover nearby ELK-BLEDOM strips (--debug lists all BLE adverts)
5859
pair <device-id> Save a device as the default target
60+
ensure [--color <#RRGGBB>] Verify connection; reconnect/repair-pair/power-on if needed, then set colour
61+
(default colour: #ffffff)
5962
on Power on the saved device
6063
off Power off the saved device
6164
color <#RRGGBB | R G B> Set RGB color
@@ -146,6 +149,29 @@ private static async Task<int> PairAsync(string[] args)
146149
return 0;
147150
}
148151

152+
private static async Task<int> EnsureAsync(string[] args)
153+
{
154+
var colorHex = ExtractOption(args, "--color") ?? "#ffffff";
155+
if (!TryParseHex(colorHex, out var r, out var g, out var b))
156+
{
157+
Console.Error.WriteLine("ensure: --color expects '#RRGGBB'.");
158+
return 1;
159+
}
160+
161+
using var device = await ResolveDeviceAsync(args);
162+
163+
// Restore the paired-list fast path if ConnectByIdAsync had to fall back
164+
// to an advertisement scan. PairAsync is a no-op when already paired.
165+
await device.PairWithWindowsAsync();
166+
167+
// Power on explicitly: this firmware silently drops SetColor writes that
168+
// arrive while the strip is in the off state.
169+
await device.TurnOnAsync();
170+
await device.SetColorAsync(r, g, b);
171+
Console.WriteLine($"Ensured {device.Name} ({device.Id}) on, colour #{r:X2}{g:X2}{b:X2}.");
172+
return 0;
173+
}
174+
149175
private static async Task<int> SimpleCommandAsync(string[] args, Func<ElkBledomDevice, Task> action)
150176
{
151177
using var device = await ResolveDeviceAsync(args);

0 commit comments

Comments
 (0)