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.
- hook_my_lichess-0.1.0/.claude/settings.json +31 -0
- hook_my_lichess-0.1.0/.claude-plugin/marketplace.json +21 -0
- hook_my_lichess-0.1.0/.claude-plugin/plugin.json +12 -0
- hook_my_lichess-0.1.0/.env.example +3 -0
- hook_my_lichess-0.1.0/.github/workflows/release.yml +23 -0
- hook_my_lichess-0.1.0/.gitignore +8 -0
- hook_my_lichess-0.1.0/CLAUDE.md +148 -0
- hook_my_lichess-0.1.0/PKG-INFO +180 -0
- hook_my_lichess-0.1.0/README.md +150 -0
- hook_my_lichess-0.1.0/docs/v5-wrapper-vs-v6-sidecar.md +388 -0
- hook_my_lichess-0.1.0/hooks/_resolve.sh +18 -0
- hook_my_lichess-0.1.0/hooks/hooks.json +29 -0
- hook_my_lichess-0.1.0/hooks/on-prompt.sh +40 -0
- hook_my_lichess-0.1.0/hooks/on-start.sh +20 -0
- hook_my_lichess-0.1.0/hooks/on-stop.sh +12 -0
- hook_my_lichess-0.1.0/lichess_puzzle/__init__.py +3 -0
- hook_my_lichess-0.1.0/lichess_puzzle/__main__.py +3 -0
- hook_my_lichess-0.1.0/lichess_puzzle/api.py +48 -0
- hook_my_lichess-0.1.0/lichess_puzzle/board.py +63 -0
- hook_my_lichess-0.1.0/lichess_puzzle/cli.py +82 -0
- hook_my_lichess-0.1.0/lichess_puzzle/engine.py +135 -0
- hook_my_lichess-0.1.0/lichess_puzzle/overlay.py +490 -0
- hook_my_lichess-0.1.0/lichess_puzzle/render.py +62 -0
- hook_my_lichess-0.1.0/lichess_puzzle/settings.py +76 -0
- hook_my_lichess-0.1.0/lichess_puzzle/sidecar.py +726 -0
- hook_my_lichess-0.1.0/pyproject.toml +46 -0
- hook_my_lichess-0.1.0/settings.example.json +4 -0
- hook_my_lichess-0.1.0/settings.json +4 -0
- hook_my_lichess-0.1.0/uv.lock +379 -0
|
@@ -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,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,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.
|