hook-my-lichess 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,31 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "$CLAUDE_PROJECT_DIR/hooks/on-prompt.sh",
9
+ "timeout": 5
10
+ },
11
+ {
12
+ "type": "command",
13
+ "command": "$CLAUDE_PROJECT_DIR/hooks/on-start.sh",
14
+ "timeout": 5
15
+ }
16
+ ]
17
+ }
18
+ ],
19
+ "Stop": [
20
+ {
21
+ "hooks": [
22
+ {
23
+ "type": "command",
24
+ "command": "$CLAUDE_PROJECT_DIR/hooks/on-stop.sh",
25
+ "timeout": 5
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ }
31
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "hml",
3
+ "owner": {
4
+ "name": "0JCRG0",
5
+ "url": "https://github.com/0JCRG0"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "hook-my-lichess",
10
+ "source": ".",
11
+ "description": "Lichess puzzle overlay that appears in your terminal while Claude is working.",
12
+ "version": "0.1.0",
13
+ "author": {
14
+ "name": "0JCRG0"
15
+ },
16
+ "homepage": "https://github.com/0JCRG0/hook-my-lichess",
17
+ "license": "MIT",
18
+ "keywords": ["lichess", "chess", "puzzle", "overlay", "kitty-graphics"]
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "hook-my-lichess",
3
+ "version": "0.1.0",
4
+ "description": "Lichess puzzle overlay that appears in your terminal while Claude is working.",
5
+ "author": {
6
+ "name": "0JCRG0"
7
+ },
8
+ "homepage": "https://github.com/0JCRG0/hook-my-lichess",
9
+ "repository": "https://github.com/0JCRG0/hook-my-lichess",
10
+ "license": "MIT",
11
+ "keywords": ["lichess", "chess", "puzzle", "overlay", "kitty-graphics"]
12
+ }
@@ -0,0 +1,3 @@
1
+ # Get a personal API token at https://lichess.org/account/oauth/token
2
+ # Scope needed: puzzle:read (and puzzle:write if you flip rated to true).
3
+ LICHESS_TOKEN=
@@ -0,0 +1,23 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+ - name: Build wheel and sdist
19
+ run: |
20
+ python -m pip install --upgrade build
21
+ python -m build
22
+ - name: Publish to PyPI
23
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ .env
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .DS_Store
@@ -0,0 +1,148 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Setup and common commands
6
+
7
+ ```bash
8
+ # One-time setup (project uses an in-repo venv).
9
+ python3 -m venv .venv
10
+ .venv/bin/pip install -e .
11
+ cp .env.example .env # then put a Lichess token in LICHESS_TOKEN
12
+
13
+ # The product. Two pieces:
14
+ # 1. The hooks in .claude/settings.json wire UserPromptSubmit/Stop
15
+ # to .claude/hooks/{on-prompt,on-start,on-stop}.sh, which call
16
+ # `hml-overlay`. Drop those hooks (with absolute paths) into
17
+ # ~/.claude/settings.json to make the overlay appear in any
18
+ # project.
19
+ # 2. `claude` itself — run it normally. There is no wrapper binary.
20
+ .venv/bin/claude # or just `claude` if the venv is activated
21
+
22
+ # Standalone puzzle TUI — no overlay, no Claude. Useful for
23
+ # exercising engine.py / board.py / api.py / render.py.
24
+ .venv/bin/lichess-puzzle
25
+ .venv/bin/lichess-puzzle --no-submit # don't POST result to Lichess
26
+ .venv/bin/lichess-puzzle --rated # report as rated (puzzle:write)
27
+ ```
28
+
29
+ There is no test suite, linter, or formatter wired up. The only
30
+ verification path is manual: install/enable the plugin, submit a
31
+ prompt in Claude Code, and confirm the puzzle appears.
32
+
33
+ `HML_FORCE_OVERLAY=1` bypasses the terminal-detection heuristic and
34
+ forces the overlay path on (useful when debugging on terminals that
35
+ should support Kitty graphics but aren't detected).
36
+
37
+ ## Architecture
38
+
39
+ One sidecar daemon, one engine, plus a shared overlay layer.
40
+
41
+ - `lichess_puzzle/engine.py` — `PuzzleSession`, an I/O-agnostic state
42
+ machine that owns the board, current solution index, and `try_move()`
43
+ semantics (UCI/SAN parsing via `python-chess`, hint/solve/quit
44
+ commands, auto-playing the opponent's reply on a correct move). Both
45
+ the standalone CLI and the sidecar drive it. `_parse()` tries SAN
46
+ first, then falls back to UCI; UCI input is `.lower()`'d before
47
+ parsing but SAN is not, so `nf3` fails while `Nf3` and `g1f3` both
48
+ work.
49
+ - `lichess_puzzle/board.py` — `parse_puzzle()` turns a Lichess
50
+ `/api/puzzle/next` payload into a `Puzzle` dataclass. Lichess
51
+ convention: replay `pgn[:initialPly]` to reach the position, then
52
+ push `pgn[initialPly]` as the opponent's *setup* move; `solution[0]`
53
+ is the user's first move.
54
+ - `lichess_puzzle/api.py` — thin `httpx` wrapper around
55
+ `/api/puzzle/next` and `/api/puzzle/batch/mix`. Loads `LICHESS_TOKEN`
56
+ from `.env` at the project root via `python-dotenv`.
57
+ - `lichess_puzzle/render.py` — ANSI Unicode board renderer used by the
58
+ standalone CLI only.
59
+ - `lichess_puzzle/cli.py` — standalone `lichess-puzzle` TUI (plain
60
+ stdin loop, ANSI rendering).
61
+ - `lichess_puzzle/overlay.py` — Kitty-graphics PNG renderer + protocol
62
+ helpers. Owns `OverlaySpec` (all dimensions live there;
63
+ `from_scale()` produces a scaled spec from `settings.size`),
64
+ `is_supported()` (capability sniff), and `kitty_transmit` /
65
+ `kitty_place` / `kitty_delete` (the three Kitty escapes the sidecar
66
+ sends).
67
+ - `lichess_puzzle/settings.py` — Pydantic v2 settings loader. Looks at
68
+ `$HML_CONFIG`, then `<cwd>/hml.json`, then
69
+ `~/.config/hml/settings.json`. `size` accepts a preset
70
+ (`small|medium|large|xl|xxl`) or a positive numeric scale; `position`
71
+ accepts a preset (`top-right|top-left|bottom-right|bottom-left|center`)
72
+ or a `[row, col]` tuple (1-indexed cells). Invalid configs print a
73
+ warning and fall back to defaults. `settings.example.json` at the
74
+ repo root is a working sample.
75
+ - `lichess_puzzle/sidecar.py` — the `hml-overlay` daemon. **This is
76
+ where the lifecycle/IPC design lives.**
77
+
78
+ ### How the sidecar overlays a puzzle on Claude's TUI (v6)
79
+
80
+ This is the thing to understand before editing `sidecar.py`:
81
+
82
+ 1. Claude Code owns its terminal end-to-end — there is **no PTY proxy**.
83
+ The sidecar is a sibling process that opens the controlling tty for
84
+ writes and emits Kitty graphics escapes. We never see or buffer
85
+ Claude's I/O.
86
+ 2. **Lifecycle.** `hml-overlay start` (UserPromptSubmit hook) captures
87
+ `os.ttyname(0)` *before* daemonizing, double-forks, redirects
88
+ stdin/stdout/stderr to /dev/null, and re-opens the saved tty path
89
+ (NOT `/dev/tty` — after `setsid()` we have no controlling terminal).
90
+ The daemon writes a per-tty PID file to `~/.cache/hml/overlay-<key>.pid`
91
+ and binds a per-tty Unix datagram socket at
92
+ `/tmp/hml-overlay-<key>.sock` (key = sha1 of tty name, first 12 hex
93
+ chars). One daemon per controlling terminal so two Claudes in two
94
+ panes coexist. `start` is idempotent — if a daemon is already alive
95
+ it just sends `WORKING` over the socket and exits.
96
+ 3. **Redraw cadence.** The daemon's main loop sits on a `selectors`
97
+ poll with a 16 ms (~60 Hz) timeout. Every tick it re-emits
98
+ `kitty_place(IMAGE_ID=1729, placement_id=1)` to the tty, wrapped in
99
+ `ESC 7` / `ESC 8` (cursor save/restore) and a cursor `goto` to the
100
+ anchor. Kitty docs guarantee that an `a=p` with the same `(i, p)`
101
+ replaces the previous placement without flicker, so we don't need a
102
+ delete-then-place dance for re-anchors.
103
+ 4. **Image lifecycle.**
104
+ - On daemon start: emit a "fetching…" frame, kick off the API
105
+ fetch in a background thread, then transmit again with the
106
+ parsed puzzle (`a=T,C=1,c=…,r=…`). `C=1` so the cursor doesn't
107
+ move; `c`/`r` clamp the image to the available cell box.
108
+ - Every 16 ms tick: re-place the existing image so it snaps back
109
+ above scrolls.
110
+ - On `IDLE`: regenerate the image with the "✓ Claude is done"
111
+ banner. The daemon does NOT exit; the user keeps solving.
112
+ - On `WORKING` while a celebration is in progress (puzzle just
113
+ solved): cancel the celebration timer, fetch a new puzzle, redraw.
114
+ - On `MOVE <text>` / `HINT` / `SOLVE` / `QUIT`: drive
115
+ `PuzzleSession`, regenerate, retransmit.
116
+ - On puzzle finish (lost, gave up, or celebration timer expired):
117
+ delete the image with `kitty_delete` (`a=d,d=I`) and exit.
118
+ - On SIGTERM/SIGINT/SIGHUP or tty going away (`os.write` ENXIO/EIO):
119
+ same delete + clean exit.
120
+ - **Don't reintroduce DECSTBM** (`ESC[1;Nr`). Earlier designs used
121
+ scroll regions or PTY shrinks; both were brittle.
122
+ 5. **Move-input UX is a slash-command interception.**
123
+ `.claude/hooks/on-prompt.sh` (a UserPromptSubmit hook ordered
124
+ *before* `on-start.sh`) reads the JSON event, extracts the prompt,
125
+ and matches against:
126
+ - `^/p[[:space:]]+(.+)$` → forward to `hml-overlay move <text>`
127
+ - `^/(hint|solve|quit)$` → forward to `hml-overlay <cmd>`
128
+ On a match it prints `{"decision":"block","reason":"…"}` and
129
+ exits 0, suppressing the prompt before Claude sees it. Anything
130
+ else passes through untouched (including prompts that contain
131
+ `%` — no false-positive interception).
132
+ 6. **Puzzle fetch is async** (`threading.Thread` → `queue.Queue`)
133
+ so the redraw timer stays smooth.
134
+ 7. **On terminal resize** (`SIGWINCH`): refresh layout, rebuild the
135
+ spec at the new size, and retransmit.
136
+
137
+ ### Hook integration
138
+
139
+ `.claude/settings.json` wires three hook scripts using
140
+ `$CLAUDE_PROJECT_DIR`. The `UserPromptSubmit` block runs `on-prompt.sh`
141
+ *then* `on-start.sh` — the first intercepts puzzle slash commands,
142
+ the second nudges/spawns the daemon (always; the prompt hook's
143
+ `block` decision only suppresses the prompt, not subsequent hooks).
144
+ The `Stop` block runs `on-stop.sh`, which sends `IDLE`.
145
+
146
+ To make the overlay appear in *any* project, copy the `hooks` block
147
+ to `~/.claude/settings.json` with absolute paths to the hook scripts
148
+ in this repo.
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: hook-my-lichess
3
+ Version: 0.1.0
4
+ Summary: A Claude Code hook that opens a Lichess puzzle in your terminal while Claude is working.
5
+ Project-URL: Homepage, https://github.com/0JCRG0/hook-my-lichess
6
+ Project-URL: Repository, https://github.com/0JCRG0/hook-my-lichess
7
+ Project-URL: Issues, https://github.com/0JCRG0/hook-my-lichess/issues
8
+ Author: JCRG
9
+ License: MIT
10
+ Keywords: chess,claude-code,kitty-graphics,lichess,puzzle,terminal
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Games/Entertainment :: Board Games
22
+ Classifier: Topic :: Terminals
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: chess>=1.10
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: pillow>=10.0
27
+ Requires-Dist: pydantic>=2.0
28
+ Requires-Dist: python-dotenv>=1.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # hook-my-lichess
32
+
33
+ A Lichess puzzle that floats over the **same terminal** Claude Code
34
+ is running in. While Claude is working, the puzzle hovers in the
35
+ top-right corner. You play moves by typing `p:<move>` as a prompt —
36
+ **including while Claude is mid-response** — and the board updates
37
+ live, no PTY proxy, no wrapper binary in the launch path.
38
+
39
+ ## How it works (v6, sidecar architecture)
40
+
41
+ Claude Code owns its terminal end-to-end. There is no wrapper. The
42
+ overlay lives in a sibling process:
43
+
44
+ - **Hook-launched daemon.** A `UserPromptSubmit` hook calls
45
+ `hml-overlay start`, which double-forks a Python daemon that opens
46
+ `/dev/tty` for writes and emits Kitty graphics escapes. Claude is
47
+ never resized, never has its I/O intercepted.
48
+ - **Independent 16 ms redraw clock.** The daemon re-emits
49
+ `kitty_place(image_id, p=1)` on its own timer, decoupled from
50
+ Claude's stdout — so the image snaps back above scrolls without
51
+ waiting for Claude to emit a byte.
52
+ - **Two input surfaces.** A turn-boundary path via the
53
+ `UserPromptSubmit` hook (`p:<move>` is intercepted with
54
+ `decision: "block"` so the prompt never reaches Claude). And a
55
+ **mid-turn path** via the daemon tailing Claude Code's session
56
+ JSONL transcript at `~/.claude/projects/<project>/<session>.jsonl`
57
+ for `queue-operation enqueue` and `attachment.queued_command`
58
+ records — Claude Code logs queued user input the moment you type
59
+ it, so the daemon dispatches mid-turn moves with no documented
60
+ hook involvement at all. An 8-second dedup window swallows the
61
+ duplicate when `UserPromptSubmit` later replays the same prompt.
62
+
63
+ See `docs/v5-wrapper-vs-v6-sidecar.md` for the full architecture
64
+ comparison and the bugs that drove the rewrite.
65
+
66
+ ## Install
67
+
68
+ `hook-my-lichess` is distributed as a Claude Code plugin. Two slash
69
+ commands and you're done.
70
+
71
+ **Prereqs (one-time):**
72
+
73
+ ```bash
74
+ # uv runs the Python daemon on demand and caches it
75
+ curl -LsSf https://astral.sh/uv/install.sh | sh
76
+
77
+ # Lichess personal token, see https://lichess.org/account/oauth/token
78
+ export LICHESS_TOKEN=lip_xxxxxxxxxxxxxxxx # add to your shell rc
79
+ ```
80
+
81
+ **Install in Claude Code:**
82
+
83
+ ```
84
+ /plugin marketplace add 0JCRG0/hook-my-lichess
85
+ /plugin install hook-my-lichess@hml
86
+ ```
87
+
88
+ That's it. The next prompt you submit will fire the hooks; `uvx`
89
+ fetches `hook-my-lichess` from PyPI on first run (~2 s) and caches
90
+ it. Subsequent invocations are instant.
91
+
92
+ The daemon auto-detects whether the terminal supports Kitty graphics
93
+ (Ghostty / Kitty / WezTerm); on terminals that don't (iTerm2,
94
+ Terminal.app, tmux), it exits silently — Claude works as normal.
95
+
96
+ ## Playing the puzzle
97
+
98
+ Submit any prompt to Claude. The board appears in the top-right
99
+ within ~1 s.
100
+
101
+ Type a puzzle command **as your prompt**:
102
+
103
+ - `p:e2e4` — submit a UCI move
104
+ - `p:Nf3` — submit a SAN move (piece letters uppercase: `Nf3`, not `nf3`)
105
+ - `p:hint` — hint (which square the piece moves from)
106
+ - `p:solve` — give up and reveal the move
107
+ - `p:quit` — close the puzzle
108
+
109
+ The hook intercepts these (`decision: "block"`) so Claude never
110
+ sees them. **You can also type them while Claude is mid-response** —
111
+ they'll queue, the daemon's transcript tailer will pick them up
112
+ within ~100 ms, and the board updates without waiting for Claude's
113
+ turn to finish. (Anything that doesn't start with `p:` passes
114
+ through to Claude untouched.)
115
+
116
+ When Claude finishes its turn, the banner flips to "✓ Claude is done"
117
+ and you can keep solving at your own pace.
118
+
119
+ ## Customizing the board (size & position)
120
+
121
+ Generate a default settings file:
122
+
123
+ ```bash
124
+ uvx --from hook-my-lichess hml-overlay init-config
125
+ # wrote default settings to ~/.config/hml/settings.json
126
+ ```
127
+
128
+ Edit `~/.config/hml/settings.json`:
129
+
130
+ ```json
131
+ {
132
+ "size": "xxl",
133
+ "position": "center"
134
+ }
135
+ ```
136
+
137
+ - **`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`).
138
+ - **`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]`).
139
+
140
+ The daemon checks three locations in order: `$HML_CONFIG`, then
141
+ `<cwd>/hml.json`, then `~/.config/hml/settings.json`. The per-user
142
+ file is the one you usually want.
143
+
144
+ ## Developing locally
145
+
146
+ If you want to hack on the overlay itself instead of just using it:
147
+
148
+ ```bash
149
+ git clone https://github.com/0JCRG0/hook-my-lichess
150
+ cd hook-my-lichess
151
+ python3 -m venv .venv
152
+ .venv/bin/pip install -e .
153
+ cp .env.example .env # put LICHESS_TOKEN here
154
+ ```
155
+
156
+ The hooks in this repo's `.claude/settings.json` reference
157
+ `$CLAUDE_PROJECT_DIR/hooks/*.sh`, and those scripts prefer the local
158
+ `.venv/bin/hml-overlay` over `uvx` when present — so working inside
159
+ this repo always uses your in-tree code, no rebuild needed.
160
+
161
+ If you also have the marketplace plugin installed, disable it for
162
+ this repo (`/plugin disable hook-my-lichess`) so hooks don't double-fire.
163
+
164
+ ## Force-enable the overlay (debugging)
165
+
166
+ Set `HML_FORCE_OVERLAY=1` to bypass the terminal-detection heuristic
167
+ and always emit Kitty graphics escapes.
168
+
169
+ `HML_DEBUG=1` writes diagnostic lines to `/tmp/hml-sidecar-debug.log`.
170
+
171
+ ## Standalone puzzle (no Claude, no overlay)
172
+
173
+ Same engine, plain stdin loop:
174
+
175
+ ```bash
176
+ .venv/bin/lichess-puzzle
177
+ ```
178
+
179
+ Useful for exercising `engine.py`, `board.py`, `api.py`, `render.py`
180
+ without touching the overlay code path.
@@ -0,0 +1,150 @@
1
+ # hook-my-lichess
2
+
3
+ A Lichess puzzle that floats over the **same terminal** Claude Code
4
+ is running in. While Claude is working, the puzzle hovers in the
5
+ top-right corner. You play moves by typing `p:<move>` as a prompt —
6
+ **including while Claude is mid-response** — and the board updates
7
+ live, no PTY proxy, no wrapper binary in the launch path.
8
+
9
+ ## How it works (v6, sidecar architecture)
10
+
11
+ Claude Code owns its terminal end-to-end. There is no wrapper. The
12
+ overlay lives in a sibling process:
13
+
14
+ - **Hook-launched daemon.** A `UserPromptSubmit` hook calls
15
+ `hml-overlay start`, which double-forks a Python daemon that opens
16
+ `/dev/tty` for writes and emits Kitty graphics escapes. Claude is
17
+ never resized, never has its I/O intercepted.
18
+ - **Independent 16 ms redraw clock.** The daemon re-emits
19
+ `kitty_place(image_id, p=1)` on its own timer, decoupled from
20
+ Claude's stdout — so the image snaps back above scrolls without
21
+ waiting for Claude to emit a byte.
22
+ - **Two input surfaces.** A turn-boundary path via the
23
+ `UserPromptSubmit` hook (`p:<move>` is intercepted with
24
+ `decision: "block"` so the prompt never reaches Claude). And a
25
+ **mid-turn path** via the daemon tailing Claude Code's session
26
+ JSONL transcript at `~/.claude/projects/<project>/<session>.jsonl`
27
+ for `queue-operation enqueue` and `attachment.queued_command`
28
+ records — Claude Code logs queued user input the moment you type
29
+ it, so the daemon dispatches mid-turn moves with no documented
30
+ hook involvement at all. An 8-second dedup window swallows the
31
+ duplicate when `UserPromptSubmit` later replays the same prompt.
32
+
33
+ See `docs/v5-wrapper-vs-v6-sidecar.md` for the full architecture
34
+ comparison and the bugs that drove the rewrite.
35
+
36
+ ## Install
37
+
38
+ `hook-my-lichess` is distributed as a Claude Code plugin. Two slash
39
+ commands and you're done.
40
+
41
+ **Prereqs (one-time):**
42
+
43
+ ```bash
44
+ # uv runs the Python daemon on demand and caches it
45
+ curl -LsSf https://astral.sh/uv/install.sh | sh
46
+
47
+ # Lichess personal token, see https://lichess.org/account/oauth/token
48
+ export LICHESS_TOKEN=lip_xxxxxxxxxxxxxxxx # add to your shell rc
49
+ ```
50
+
51
+ **Install in Claude Code:**
52
+
53
+ ```
54
+ /plugin marketplace add 0JCRG0/hook-my-lichess
55
+ /plugin install hook-my-lichess@hml
56
+ ```
57
+
58
+ That's it. The next prompt you submit will fire the hooks; `uvx`
59
+ fetches `hook-my-lichess` from PyPI on first run (~2 s) and caches
60
+ it. Subsequent invocations are instant.
61
+
62
+ The daemon auto-detects whether the terminal supports Kitty graphics
63
+ (Ghostty / Kitty / WezTerm); on terminals that don't (iTerm2,
64
+ Terminal.app, tmux), it exits silently — Claude works as normal.
65
+
66
+ ## Playing the puzzle
67
+
68
+ Submit any prompt to Claude. The board appears in the top-right
69
+ within ~1 s.
70
+
71
+ Type a puzzle command **as your prompt**:
72
+
73
+ - `p:e2e4` — submit a UCI move
74
+ - `p:Nf3` — submit a SAN move (piece letters uppercase: `Nf3`, not `nf3`)
75
+ - `p:hint` — hint (which square the piece moves from)
76
+ - `p:solve` — give up and reveal the move
77
+ - `p:quit` — close the puzzle
78
+
79
+ The hook intercepts these (`decision: "block"`) so Claude never
80
+ sees them. **You can also type them while Claude is mid-response** —
81
+ they'll queue, the daemon's transcript tailer will pick them up
82
+ within ~100 ms, and the board updates without waiting for Claude's
83
+ turn to finish. (Anything that doesn't start with `p:` passes
84
+ through to Claude untouched.)
85
+
86
+ When Claude finishes its turn, the banner flips to "✓ Claude is done"
87
+ and you can keep solving at your own pace.
88
+
89
+ ## Customizing the board (size & position)
90
+
91
+ Generate a default settings file:
92
+
93
+ ```bash
94
+ uvx --from hook-my-lichess hml-overlay init-config
95
+ # wrote default settings to ~/.config/hml/settings.json
96
+ ```
97
+
98
+ Edit `~/.config/hml/settings.json`:
99
+
100
+ ```json
101
+ {
102
+ "size": "xxl",
103
+ "position": "center"
104
+ }
105
+ ```
106
+
107
+ - **`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`).
108
+ - **`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]`).
109
+
110
+ The daemon checks three locations in order: `$HML_CONFIG`, then
111
+ `<cwd>/hml.json`, then `~/.config/hml/settings.json`. The per-user
112
+ file is the one you usually want.
113
+
114
+ ## Developing locally
115
+
116
+ If you want to hack on the overlay itself instead of just using it:
117
+
118
+ ```bash
119
+ git clone https://github.com/0JCRG0/hook-my-lichess
120
+ cd hook-my-lichess
121
+ python3 -m venv .venv
122
+ .venv/bin/pip install -e .
123
+ cp .env.example .env # put LICHESS_TOKEN here
124
+ ```
125
+
126
+ The hooks in this repo's `.claude/settings.json` reference
127
+ `$CLAUDE_PROJECT_DIR/hooks/*.sh`, and those scripts prefer the local
128
+ `.venv/bin/hml-overlay` over `uvx` when present — so working inside
129
+ this repo always uses your in-tree code, no rebuild needed.
130
+
131
+ If you also have the marketplace plugin installed, disable it for
132
+ this repo (`/plugin disable hook-my-lichess`) so hooks don't double-fire.
133
+
134
+ ## Force-enable the overlay (debugging)
135
+
136
+ Set `HML_FORCE_OVERLAY=1` to bypass the terminal-detection heuristic
137
+ and always emit Kitty graphics escapes.
138
+
139
+ `HML_DEBUG=1` writes diagnostic lines to `/tmp/hml-sidecar-debug.log`.
140
+
141
+ ## Standalone puzzle (no Claude, no overlay)
142
+
143
+ Same engine, plain stdin loop:
144
+
145
+ ```bash
146
+ .venv/bin/lichess-puzzle
147
+ ```
148
+
149
+ Useful for exercising `engine.py`, `board.py`, `api.py`, `render.py`
150
+ without touching the overlay code path.