A Claude Code hook that opens a Lichess puzzle in your terminal while Claude is working.
Project description
hook-my-lichess
A Lichess puzzle that floats over the same terminal Claude Code
is running in. While Claude is working, the puzzle hovers in the
top-right corner. You play moves by typing p:<move> as a prompt —
including while Claude is mid-response — and the board updates
live, no PTY proxy, no wrapper binary in the launch path.
How it works (v6, sidecar architecture)
Claude Code owns its terminal end-to-end. There is no wrapper. The overlay lives in a sibling process:
- Hook-launched daemon. A
UserPromptSubmithook callshml-overlay start, which double-forks a Python daemon that opens/dev/ttyfor writes and emits Kitty graphics escapes. Claude is never resized, never has its I/O intercepted. - Independent 16 ms redraw clock. The daemon re-emits
kitty_place(image_id, p=1)on its own timer, decoupled from Claude's stdout — so the image snaps back above scrolls without waiting for Claude to emit a byte. - Two input surfaces. A turn-boundary path via the
UserPromptSubmithook (p:<move>is intercepted withdecision: "block"so the prompt never reaches Claude). And a mid-turn path via the daemon tailing Claude Code's session JSONL transcript at~/.claude/projects/<project>/<session>.jsonlforqueue-operation enqueueandattachment.queued_commandrecords — Claude Code logs queued user input the moment you type it, so the daemon dispatches mid-turn moves with no documented hook involvement at all. An 8-second dedup window swallows the duplicate whenUserPromptSubmitlater replays the same prompt.
See docs/v5-wrapper-vs-v6-sidecar.md for the full architecture
comparison and the bugs that drove the rewrite.
Install
hook-my-lichess is distributed as a Claude Code plugin. Two slash
commands and you're done.
Prereqs (one-time):
# uv runs the Python daemon on demand and caches it
curl -LsSf https://astral.sh/uv/install.sh | sh
# Lichess personal token, see https://lichess.org/account/oauth/token
export LICHESS_TOKEN=lip_xxxxxxxxxxxxxxxx # add to your shell rc
Install in Claude Code:
/plugin marketplace add 0JCRG0/hook-my-lichess
/plugin install hook-my-lichess@hml
That's it. The next prompt you submit will fire the hooks; uvx
fetches hook-my-lichess from PyPI on first run (~2 s) and caches
it. Subsequent invocations are instant.
The daemon auto-detects whether the terminal supports Kitty graphics (Ghostty / Kitty / WezTerm); on terminals that don't (iTerm2, Terminal.app, tmux), it exits silently — Claude works as normal.
Playing the puzzle
Submit any prompt to Claude. The board appears in the top-right within ~1 s.
Type a puzzle command as your prompt:
p:e2e4— submit a UCI movep:Nf3— submit a SAN move (piece letters uppercase:Nf3, notnf3)p:hint— hint (which square the piece moves from)p:solve— give up and reveal the movep:quit— close the puzzle
The hook intercepts these (decision: "block") so Claude never
sees them. You can also type them while Claude is mid-response —
they'll queue, the daemon's transcript tailer will pick them up
within ~100 ms, and the board updates without waiting for Claude's
turn to finish. (Anything that doesn't start with p: passes
through to Claude untouched.)
When Claude finishes its turn, the banner flips to "✓ Claude is done" and you can keep solving at your own pace.
Customizing the board (size & position)
Generate a default settings file:
uvx --from hook-my-lichess hml-overlay init-config
# wrote default settings to ~/.config/hml/settings.json
Edit ~/.config/hml/settings.json:
{
"size": "xxl",
"position": "center"
}
size— one of"small"(0.75×),"medium"(1×, default),"large"(1.25×),"xl"(1.5×),"xxl"(2×), or any positive number for an exact scale (e.g."size": 1.7).position— one of"top-right"(default),"top-left","bottom-right","bottom-left","center", or a[row, col]pair for an exact 1-indexed cell (e.g."position": [3, 80]).
The daemon checks three locations in order: $HML_CONFIG, then
<cwd>/hml.json, then ~/.config/hml/settings.json. The per-user
file is the one you usually want.
Developing locally
If you want to hack on the overlay itself instead of just using it:
git clone https://github.com/0JCRG0/hook-my-lichess
cd hook-my-lichess
python3 -m venv .venv
.venv/bin/pip install -e .
cp .env.example .env # put LICHESS_TOKEN here
The hooks in this repo's .claude/settings.json reference
$CLAUDE_PROJECT_DIR/hooks/*.sh, and those scripts prefer the local
.venv/bin/hml-overlay over uvx when present — so working inside
this repo always uses your in-tree code, no rebuild needed.
If you also have the marketplace plugin installed, disable it for
this repo (/plugin disable hook-my-lichess) so hooks don't double-fire.
Force-enable the overlay (debugging)
Set HML_FORCE_OVERLAY=1 to bypass the terminal-detection heuristic
and always emit Kitty graphics escapes.
HML_DEBUG=1 writes diagnostic lines to /tmp/hml-sidecar-debug.log.
Standalone puzzle (no Claude, no overlay)
Same engine, plain stdin loop:
.venv/bin/lichess-puzzle
Useful for exercising engine.py, board.py, api.py, render.py
without touching the overlay code path.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hook_my_lichess-0.1.0.tar.gz.
File metadata
- Download URL: hook_my_lichess-0.1.0.tar.gz
- Upload date:
- Size: 56.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5740cf748c34324e19396af0c33ee21bb62b06321dfcf4853dd84d3f1de630e7
|
|
| MD5 |
eb7ba5c0e0da3fb1306a5b5a3f206d76
|
|
| BLAKE2b-256 |
f710090f1f31a1a89ca13959eec7d9916cc9915ecb167f0163446e6045c9e4d6
|
Provenance
The following attestation bundles were made for hook_my_lichess-0.1.0.tar.gz:
Publisher:
release.yml on 0JCRG0/hook-my-lichess
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hook_my_lichess-0.1.0.tar.gz -
Subject digest:
5740cf748c34324e19396af0c33ee21bb62b06321dfcf4853dd84d3f1de630e7 - Sigstore transparency entry: 2253205399
- Sigstore integration time:
-
Permalink:
0JCRG0/hook-my-lichess@971a9f53db5fdf3cf3134f430d0c0f0ef08a4987 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/0JCRG0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@971a9f53db5fdf3cf3134f430d0c0f0ef08a4987 -
Trigger Event:
push
-
Statement type:
File details
Details for the file hook_my_lichess-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hook_my_lichess-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8efee4beb4108caabfc148dfdf0a71290f8745a0e565bde2761c91510913ac60
|
|
| MD5 |
624e5e3ffff17b15c8677f06d861af84
|
|
| BLAKE2b-256 |
7474a4db990ca6ca8b9766a52735561f3cfcab71257ff92acc10ede65f5dba20
|
Provenance
The following attestation bundles were made for hook_my_lichess-0.1.0-py3-none-any.whl:
Publisher:
release.yml on 0JCRG0/hook-my-lichess
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hook_my_lichess-0.1.0-py3-none-any.whl -
Subject digest:
8efee4beb4108caabfc148dfdf0a71290f8745a0e565bde2761c91510913ac60 - Sigstore transparency entry: 2253205488
- Sigstore integration time:
-
Permalink:
0JCRG0/hook-my-lichess@971a9f53db5fdf3cf3134f430d0c0f0ef08a4987 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/0JCRG0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@971a9f53db5fdf3cf3134f430d0c0f0ef08a4987 -
Trigger Event:
push
-
Statement type: