chesspal 0.1.0 → 0.2.0

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.
Files changed (209) hide show
  1. package/README.md +45 -82
  2. package/dist/cli/chat-hook.js +1 -1
  3. package/dist/cli/command-line.d.ts +1 -2
  4. package/dist/cli/command-line.js +2 -3
  5. package/dist/commands/agents.d.ts +26 -0
  6. package/dist/commands/agents.js +72 -0
  7. package/dist/commands/ai-move.d.ts +5 -14
  8. package/dist/commands/ai-move.js +28 -35
  9. package/dist/commands/ai-say.d.ts +3 -1
  10. package/dist/commands/ai-say.js +24 -6
  11. package/dist/commands/analyse-game.d.ts +4 -0
  12. package/dist/commands/analyse-game.js +43 -16
  13. package/dist/commands/annotate-game.d.ts +4 -0
  14. package/dist/commands/annotate-game.js +65 -12
  15. package/dist/commands/answer-lines.d.ts +28 -0
  16. package/dist/commands/answer-lines.js +144 -0
  17. package/dist/commands/coach.d.ts +45 -0
  18. package/dist/commands/coach.js +344 -0
  19. package/dist/commands/context.d.ts +16 -5
  20. package/dist/commands/context.js +1 -0
  21. package/dist/commands/delete-game.d.ts +7 -0
  22. package/dist/commands/delete-game.js +30 -0
  23. package/dist/commands/delete-review.d.ts +8 -0
  24. package/dist/commands/delete-review.js +34 -0
  25. package/dist/commands/drawings.d.ts +0 -2
  26. package/dist/commands/drawings.js +5 -3
  27. package/dist/commands/end-game.js +5 -2
  28. package/dist/commands/export-game.d.ts +1 -1
  29. package/dist/commands/export-game.js +4 -3
  30. package/dist/commands/game-summary.d.ts +21 -38
  31. package/dist/commands/game-summary.js +30 -167
  32. package/dist/commands/index.d.ts +15 -0
  33. package/dist/commands/index.js +47 -2
  34. package/dist/commands/init.d.ts +3 -3
  35. package/dist/commands/init.js +4 -4
  36. package/dist/commands/lead-input.d.ts +8 -0
  37. package/dist/commands/lead-input.js +21 -0
  38. package/dist/commands/look.js +4 -1
  39. package/dist/commands/new-game.d.ts +2 -3
  40. package/dist/commands/new-game.js +7 -4
  41. package/dist/commands/play-move.d.ts +8 -15
  42. package/dist/commands/play-move.js +5 -13
  43. package/dist/commands/player-move.d.ts +2 -6
  44. package/dist/commands/player-move.js +13 -6
  45. package/dist/commands/player-say.d.ts +1 -1
  46. package/dist/commands/player-say.js +1 -1
  47. package/dist/commands/ponder.d.ts +10 -0
  48. package/dist/commands/ponder.js +33 -21
  49. package/dist/commands/position.d.ts +1 -1
  50. package/dist/commands/position.js +3 -1
  51. package/dist/commands/profiles.d.ts +13 -4
  52. package/dist/commands/profiles.js +49 -15
  53. package/dist/commands/registry.d.ts +8 -3
  54. package/dist/commands/registry.js +12 -11
  55. package/dist/commands/say.d.ts +1 -1
  56. package/dist/commands/say.js +16 -16
  57. package/dist/commands/speech.d.ts +8 -0
  58. package/dist/commands/speech.js +20 -0
  59. package/dist/commands/status.d.ts +9 -3
  60. package/dist/commands/status.js +40 -15
  61. package/dist/commands/study-games.js +1 -1
  62. package/dist/commands/study.js +3 -19
  63. package/dist/core/facts.js +15 -15
  64. package/dist/core/game.d.ts +3 -1
  65. package/dist/core/game.js +8 -4
  66. package/dist/core/illegal.js +1 -1
  67. package/dist/core/index.d.ts +5 -4
  68. package/dist/core/index.js +5 -4
  69. package/dist/core/lead.d.ts +15 -0
  70. package/dist/core/lead.js +40 -0
  71. package/dist/core/levels.d.ts +1 -1
  72. package/dist/core/levels.js +1 -1
  73. package/dist/core/moods.d.ts +0 -2
  74. package/dist/core/moods.js +2 -2
  75. package/dist/core/openings.d.ts +0 -2
  76. package/dist/core/openings.js +1 -1
  77. package/dist/core/profile.js +1 -1
  78. package/dist/core/record.d.ts +1 -1
  79. package/dist/core/record.js +1 -1
  80. package/dist/core/review.d.ts +9 -3
  81. package/dist/core/review.js +29 -9
  82. package/dist/games/analysis.d.ts +1 -1
  83. package/dist/games/archive.d.ts +24 -3
  84. package/dist/games/archive.js +96 -22
  85. package/dist/games/coach.d.ts +57 -0
  86. package/dist/games/coach.js +144 -0
  87. package/dist/games/current-game.d.ts +2 -0
  88. package/dist/games/current-game.js +7 -1
  89. package/dist/games/files.js +1 -0
  90. package/dist/games/index.d.ts +4 -3
  91. package/dist/games/index.js +4 -3
  92. package/dist/games/talk.d.ts +1 -5
  93. package/dist/games/talk.js +14 -14
  94. package/dist/home/open-folder.js +1 -1
  95. package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
  96. package/dist/host-files/agents.js +53 -0
  97. package/dist/host-files/host-files.d.ts +7 -2
  98. package/dist/host-files/host-files.js +27 -12
  99. package/dist/host-files/index.d.ts +2 -2
  100. package/dist/host-files/index.js +3 -3
  101. package/dist/index.d.ts +1 -3
  102. package/dist/index.js +1 -0
  103. package/dist/page/app/anchor.js +88 -0
  104. package/dist/page/{app.js → app/app.js} +112 -230
  105. package/dist/page/app/keys.js +94 -0
  106. package/dist/page/app/phase.js +30 -0
  107. package/dist/page/app/served.js +73 -0
  108. package/dist/page/avatars/avatars.js +19 -0
  109. package/dist/page/avatars/thought.js +10 -0
  110. package/dist/page/base.css +160 -0
  111. package/dist/page/board/board.css +959 -0
  112. package/dist/page/board/board.js +125 -0
  113. package/dist/page/{explain.js → board/explain.js} +1 -1
  114. package/dist/page/board/marks.js +160 -0
  115. package/dist/page/board/motion.js +71 -0
  116. package/dist/page/board/pointer.js +239 -0
  117. package/dist/page/board/squares.js +26 -0
  118. package/dist/page/dialogs/analyses.js +195 -0
  119. package/dist/page/{dialog.js → dialogs/dialog.js} +1 -1
  120. package/dist/page/dialogs/dialogs.css +1228 -0
  121. package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
  122. package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
  123. package/dist/page/dialogs/search.js +31 -0
  124. package/dist/page/dialogs/settings.js +42 -0
  125. package/dist/page/dialogs/studied.js +39 -0
  126. package/dist/page/dialogs/study.js +51 -0
  127. package/dist/page/{badges.js → game/badges.js} +1 -1
  128. package/dist/page/{browse.js → game/browse.js} +1 -1
  129. package/dist/page/game/card-fold.js +40 -0
  130. package/dist/page/game/column.js +51 -0
  131. package/dist/page/{ending.js → game/ending.js} +2 -2
  132. package/dist/page/game/game.css +577 -0
  133. package/dist/page/{move-list.js → game/move-list.js} +10 -6
  134. package/dist/page/{plies.js → game/plies.js} +30 -10
  135. package/dist/page/game/result-tile.js +14 -0
  136. package/dist/page/helps/helps.css +689 -0
  137. package/dist/page/helps/helps.js +220 -0
  138. package/dist/page/helps/view.js +127 -0
  139. package/dist/page/layout/fold-tab.js +11 -0
  140. package/dist/page/{folds.js → layout/folds.js} +1 -1
  141. package/dist/page/{header.js → layout/header.js} +5 -4
  142. package/dist/page/layout/layout.css +834 -0
  143. package/dist/page/lead/lead.js +82 -0
  144. package/dist/page/lead/view.js +38 -0
  145. package/dist/page/main.js +2 -2
  146. package/dist/page/pieces/CREDITS.md +12 -29
  147. package/dist/page/pieces/capablanca-small.svg +20 -0
  148. package/dist/page/pieces/capablanca.svg +22 -0
  149. package/dist/page/pieces/nitida-small.svg +3 -2
  150. package/dist/page/pieces/nitida.svg +3 -2
  151. package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
  152. package/dist/page/pieces/torretta-small.svg +15 -14
  153. package/dist/page/pieces/torretta.svg +17 -15
  154. package/dist/page/{player-card.js → players/card.js} +2 -6
  155. package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
  156. package/dist/page/{profile.js → profile/profile.js} +9 -5
  157. package/dist/page/{settings.js → profile/settings.js} +5 -4
  158. package/dist/page/style.css +9 -4398
  159. package/dist/page/talk/talk.css +958 -0
  160. package/dist/page/{talk.js → talk/talk.js} +39 -46
  161. package/dist/page/text.js +137 -19
  162. package/dist/page/theme.css +292 -0
  163. package/dist/profiles/index.d.ts +1 -1
  164. package/dist/profiles/profiles.d.ts +15 -4
  165. package/dist/profiles/profiles.js +24 -7
  166. package/dist/server/bell.d.ts +10 -2
  167. package/dist/server/bell.js +28 -12
  168. package/dist/server/caller.d.ts +14 -3
  169. package/dist/server/caller.js +44 -11
  170. package/dist/server/mcp.d.ts +1 -2
  171. package/dist/server/mcp.js +42 -14
  172. package/dist/server/page/analyses.d.ts +23 -0
  173. package/dist/server/page/analyses.js +65 -0
  174. package/dist/server/page/files.d.ts +5 -0
  175. package/dist/server/page/files.js +92 -0
  176. package/dist/server/page/game.d.ts +6 -0
  177. package/dist/server/page/game.js +381 -0
  178. package/dist/server/{http.d.ts → page/http.d.ts} +13 -0
  179. package/dist/server/{http.js → page/http.js} +11 -0
  180. package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
  181. package/dist/server/page/page.js +229 -0
  182. package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
  183. package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
  184. package/instructions/analysis.md +4 -0
  185. package/instructions/analyst-agent.md +8 -0
  186. package/instructions/analyst.md +3 -7
  187. package/instructions/coach.md +11 -0
  188. package/instructions/opponent.md +4 -4
  189. package/package.json +1 -1
  190. package/dist/host-files/ponder-agents.js +0 -40
  191. package/dist/page/avatars.js +0 -16
  192. package/dist/page/board.js +0 -566
  193. package/dist/page/fold-tab.js +0 -9
  194. package/dist/page/game-column.js +0 -44
  195. package/dist/page/helps-view.js +0 -51
  196. package/dist/page/helps.js +0 -90
  197. package/dist/page/pieces/cburnett-small.svg +0 -20
  198. package/dist/page/pieces/cburnett.svg +0 -20
  199. package/dist/page/settings-panel.js +0 -32
  200. package/dist/page/studied.js +0 -14
  201. package/dist/page/study-dialog.js +0 -58
  202. package/dist/server/analyses.d.ts +0 -17
  203. package/dist/server/analyses.js +0 -46
  204. package/dist/server/page.js +0 -657
  205. /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
  206. /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
  207. /package/dist/page/{model-name.js → players/model-name.js} +0 -0
  208. /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
  209. /package/dist/page/{voice.js → talk/voice.js} +0 -0
@@ -0,0 +1,292 @@
1
+ /* The page's colours, light and dark, and the skins the settings choose for the board, the pieces and the marks (D-56) */
2
+
3
+ /* The page in Zoe's Salotto azzurro (D-46), light and dark. Its ground mixes the board's dark square, which the settings set before the page draws: without
4
+ them, as without scripts, the page has no ground of its own */
5
+ :root {
6
+ --page: color-mix(in oklab, var(--dark-square) 12%, #fbfbfa);
7
+ --card: color-mix(in oklab, var(--dark-square) 3%, #ffffff);
8
+ --ink: #1d2733;
9
+ --soft: #626d7b;
10
+ --line: color-mix(in oklab, var(--dark-square) 24%, #f1f1ef);
11
+ --accent: #2d6aa8;
12
+ --on-accent: #ffffff;
13
+ --quote: color-mix(in oklab, var(--dark-square) 9%, #fafafa);
14
+ --say: color-mix(in oklab, var(--dark-square) 38%, #fafafa);
15
+ --say-ink: #1d2733;
16
+ --warn-back: #fff4d6;
17
+ /* The squares, their highlights, the marks and the pieces take their colours from the skins the settings choose, further on */
18
+ --mated: rgba(214, 40, 40, 0.8);
19
+ --low-time: #c62828;
20
+ --hint: rgba(0, 0, 0, 0.14);
21
+ --explain-ink: #1d2733;
22
+ --lift-shadow: rgba(16, 28, 44, 0.32);
23
+ --ghost: 0.35;
24
+ /* The four colours of PGN's %cal and %csl, for what the AI draws (D-69) */
25
+ --drawn-green: #15a34a;
26
+ --drawn-red: #dc2626;
27
+ --drawn-yellow: #eab308;
28
+ --drawn-blue: #2563eb;
29
+ /* Ink that stays dark in both themes: on the light squares and on White's chips */
30
+ --fixed-ink: #1d2733;
31
+ /* The helps (D-78, Zoe's sketch B): one ink for all they draw, never one of the AI's colours, a fill or a ring. The board says where, the card says what */
32
+ --help-ink: #11161d;
33
+ --help-on: #fff;
34
+ --help-line: rgba(17, 22, 29, 0.78);
35
+ --help-halo: rgba(255, 255, 255, 0.85);
36
+ --torretta-body: #6d9fd1;
37
+ --torretta-shade: #4b7cb0;
38
+ --torretta-screen: #16263a;
39
+ --torretta-face: #f2f7fc;
40
+ --torretta-light: #f4c84c;
41
+ --torretta-alarm: #ef6a4c;
42
+ --torretta-ponder: #8f9a8a;
43
+ /* The badges of the moves (D-69): white signs, dark on the yellow one; each disc at least 3:1 with its sign */
44
+ --badge-ink: #fff;
45
+ /* How a game went for the player, in the Analysis window's list: the colours of the move badges (D-69) */
46
+ --result-win: var(--badge-best);
47
+ --result-loss: var(--badge-blunder);
48
+ --result-draw: var(--badge-note);
49
+ --badge-note: #7b8794;
50
+ --badge-brilliant: #0f9a9e;
51
+ --badge-great: #2f74c8;
52
+ --badge-best: #3c9a3a;
53
+ --badge-excellent: #6b9c2c;
54
+ --badge-good: #7d9a62;
55
+ --badge-book: #94704c;
56
+ --badge-inaccuracy: #e6b422;
57
+ --badge-inaccuracy-ink: #3b2c00;
58
+ --badge-mistake: #d4650f;
59
+ --badge-miss: #c2407d;
60
+ --badge-blunder: #d22c2c;
61
+ --shadow: 0 1px 2px rgba(29, 39, 51, 0.06), 0 6px 18px rgba(29, 39, 51, 0.06);
62
+ /* On a light page the board keeps a thin edge in its dark squares colour, and a deeper shadow, so its light squares do not melt into the page */
63
+ --board-shadow: 0 0 0 1px color-mix(in oklab, var(--dark-square) 55%, transparent), 0 2px 4px rgba(29, 39, 51, 0.1), 0 10px 28px rgba(29, 39, 51, 0.12);
64
+ --gap: 20px;
65
+ --left: 300px;
66
+ --right: 300px;
67
+ /* The board's least size beside the talk: room for the AI's words, the box and both cards; a lower window scrolls */
68
+ --least: 440px;
69
+ --tab: 16px;
70
+ --rail: 56px;
71
+ --coin: 40px;
72
+ /* How far from the board's top and bottom edges the faces sit, in the cards and folded, and the tabs */
73
+ --ear: 36px;
74
+ --tuck: 10px;
75
+ --bar: 36px;
76
+ /* The cards' corners, as the board's; the room between the cards, and between them and the board */
77
+ --radius: 6px;
78
+ --between: 10px;
79
+ --bubble: 14px;
80
+ color-scheme: light;
81
+ }
82
+
83
+ :root[data-theme="dark"] {
84
+ --page: color-mix(in oklab, var(--dark-square) 13%, #0b0d10);
85
+ --card: color-mix(in oklab, var(--dark-square) 16%, #14171b);
86
+ --ink: #e6ebf1;
87
+ --soft: #8d98a7;
88
+ --line: color-mix(in oklab, var(--dark-square) 24%, #1b1f24);
89
+ --accent: #7cb0e4;
90
+ --on-accent: #0f1a28;
91
+ --low-time: #ff6b6b;
92
+ --quote: color-mix(in oklab, var(--dark-square) 20%, #1a1d22);
93
+ --say: color-mix(in oklab, var(--dark-square) 34%, #15181c);
94
+ --say-ink: #e6ebf1;
95
+ --warn-back: #2a2616;
96
+ --hint: rgba(0, 0, 0, 0.17);
97
+ --lift-shadow: rgba(0, 0, 0, 0.5);
98
+ --torretta-screen: #0f1a28;
99
+ --badge-great: #3577c9;
100
+ --badge-book: #9c7650;
101
+ --badge-miss: #c9478a;
102
+ --badge-blunder: #d93434;
103
+ --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 22px rgba(0, 0, 0, 0.25);
104
+ --board-shadow: var(--shadow);
105
+ color-scheme: dark;
106
+ }
107
+
108
+ /* Zoe's palettes: on the page's root and on each swatch of the settings. Board skins, each with its own highlights */
109
+ :root[data-board="azzurro"],
110
+ [data-board="azzurro"] {
111
+ --light-square: #eaf1f8;
112
+ --dark-square: #86abd0;
113
+ --last-move: rgba(255, 206, 70, 0.65);
114
+ --selected: rgba(255, 190, 50, 0.85);
115
+ --hover-edge: #f4c84c;
116
+ }
117
+
118
+ :root[data-theme="dark"][data-board="azzurro"],
119
+ :root[data-theme="dark"] [data-board="azzurro"] {
120
+ --light-square: #a9bccd;
121
+ --dark-square: #61809e;
122
+ }
123
+
124
+ :root[data-board="verde"],
125
+ [data-board="verde"] {
126
+ --light-square: #eeeed2;
127
+ --dark-square: #769656;
128
+ --last-move: rgba(255, 255, 51, 0.5);
129
+ --selected: rgba(255, 236, 40, 0.75);
130
+ --hover-edge: #f6f669;
131
+ }
132
+
133
+ :root[data-theme="dark"][data-board="verde"],
134
+ :root[data-theme="dark"] [data-board="verde"] {
135
+ --light-square: #c3c7a8;
136
+ --dark-square: #5f7c45;
137
+ }
138
+
139
+ :root[data-board="noce"],
140
+ [data-board="noce"] {
141
+ --light-square: #e9d8bd;
142
+ --dark-square: #a6866a;
143
+ --last-move: rgba(200, 212, 40, 0.55);
144
+ --selected: rgba(185, 205, 20, 0.75);
145
+ --hover-edge: #cdd26a;
146
+ }
147
+
148
+ :root[data-theme="dark"][data-board="noce"],
149
+ :root[data-theme="dark"] [data-board="noce"] {
150
+ --light-square: #c4b096;
151
+ --dark-square: #80654d;
152
+ }
153
+
154
+ :root[data-board="ardesia"],
155
+ [data-board="ardesia"] {
156
+ --light-square: #dfe3e8;
157
+ --dark-square: #8b97a4;
158
+ --last-move: rgba(255, 206, 70, 0.6);
159
+ --selected: rgba(255, 190, 50, 0.8);
160
+ --hover-edge: #f4c84c;
161
+ }
162
+
163
+ :root[data-theme="dark"][data-board="ardesia"],
164
+ :root[data-theme="dark"] [data-board="ardesia"] {
165
+ --light-square: #a7b0ba;
166
+ --dark-square: #65707c;
167
+ }
168
+
169
+ :root[data-board="grigio"],
170
+ [data-board="grigio"] {
171
+ --light-square: #ededed;
172
+ --dark-square: #9a9a9a;
173
+ --last-move: rgba(255, 206, 70, 0.6);
174
+ --selected: rgba(255, 190, 50, 0.8);
175
+ --hover-edge: #f4c84c;
176
+ }
177
+
178
+ :root[data-theme="dark"][data-board="grigio"],
179
+ :root[data-theme="dark"] [data-board="grigio"] {
180
+ --light-square: #b9b9b9;
181
+ --dark-square: #707070;
182
+ }
183
+
184
+ :root[data-board="lavanda"],
185
+ [data-board="lavanda"] {
186
+ --light-square: #ece6f2;
187
+ --dark-square: #9a86b8;
188
+ --last-move: rgba(255, 214, 90, 0.6);
189
+ --selected: rgba(255, 200, 60, 0.8);
190
+ --hover-edge: #f4c84c;
191
+ }
192
+
193
+ :root[data-theme="dark"][data-board="lavanda"],
194
+ :root[data-theme="dark"] [data-board="lavanda"] {
195
+ --light-square: #b8adc6;
196
+ --dark-square: #6f5e8c;
197
+ }
198
+
199
+ :root[data-board="ciliegio"],
200
+ [data-board="ciliegio"] {
201
+ --light-square: #ecd3b6;
202
+ --dark-square: #9a5a3e;
203
+ --last-move: rgba(230, 220, 70, 0.55);
204
+ --selected: rgba(220, 210, 40, 0.75);
205
+ --hover-edge: #e6dc46;
206
+ }
207
+
208
+ :root[data-theme="dark"][data-board="ciliegio"],
209
+ :root[data-theme="dark"] [data-board="ciliegio"] {
210
+ --light-square: #c6ab8f;
211
+ --dark-square: #73412c;
212
+ }
213
+
214
+ :root[data-board="menta"],
215
+ [data-board="menta"] {
216
+ --light-square: #e9f3ec;
217
+ --dark-square: #7cb396;
218
+ --last-move: rgba(255, 218, 80, 0.6);
219
+ --selected: rgba(255, 204, 50, 0.8);
220
+ --hover-edge: #f4c84c;
221
+ }
222
+
223
+ :root[data-theme="dark"][data-board="menta"],
224
+ :root[data-theme="dark"] [data-board="menta"] {
225
+ --light-square: #b3c6b9;
226
+ --dark-square: #58876d;
227
+ }
228
+
229
+ :root[data-board="carta"],
230
+ [data-board="carta"] {
231
+ --light-square: #f3f1ea;
232
+ --dark-square: #b8b2a2;
233
+ --last-move: rgba(110, 165, 225, 0.5);
234
+ --selected: rgba(80, 145, 215, 0.7);
235
+ --hover-edge: #4a8ad0;
236
+ }
237
+
238
+ :root[data-theme="dark"][data-board="carta"],
239
+ :root[data-theme="dark"] [data-board="carta"] {
240
+ --light-square: #c9c5b9;
241
+ --dark-square: #8a8577;
242
+ }
243
+
244
+ /* Piece colours for Nitida and Torretta */
245
+ :root[data-pieces="classico"],
246
+ [data-pieces="classico"] {
247
+ --white-piece: #fff;
248
+ --black-piece: #000;
249
+ }
250
+
251
+ :root[data-pieces="avorio"],
252
+ [data-pieces="avorio"] {
253
+ --white-piece: #f4ead3;
254
+ --black-piece: #2a1c12;
255
+ }
256
+
257
+ :root[data-pieces="notte"],
258
+ [data-pieces="notte"] {
259
+ --white-piece: #fff;
260
+ --black-piece: #1a3050;
261
+ }
262
+
263
+ :root[data-pieces="bordeaux"],
264
+ [data-pieces="bordeaux"] {
265
+ --white-piece: #fff;
266
+ --black-piece: #5e161c;
267
+ }
268
+
269
+ /* Marks with the right button */
270
+ :root[data-marks="arancione"],
271
+ [data-marks="arancione"] {
272
+ --mark: #f07c1e;
273
+ --mark-square: rgba(240, 124, 30, 0.65);
274
+ }
275
+
276
+ :root[data-marks="rosso"],
277
+ [data-marks="rosso"] {
278
+ --mark: #d6342c;
279
+ --mark-square: rgba(214, 52, 44, 0.6);
280
+ }
281
+
282
+ :root[data-marks="blu"],
283
+ [data-marks="blu"] {
284
+ --mark: #2335a8;
285
+ --mark-square: rgba(35, 53, 168, 0.62);
286
+ }
287
+
288
+ :root[data-marks="verde"],
289
+ [data-marks="verde"] {
290
+ --mark: #0f6b30;
291
+ --mark-square: rgba(15, 107, 48, 0.7);
292
+ }
@@ -1,3 +1,3 @@
1
1
  export { type ImageType, imageTypeOf, imageTypes, ownImage, ownImageStamp, removeOwnImage, saveOwnImage, sentImageLimit } from "./avatar.ts";
2
- export { activateProfile, activeProfile, createProfile, type DrawnAvatar, drawnAvatars, ensureProfile, gameFolder, gameFolders, type Level, levels, listProfiles, nameLimit, type PlayerProfile, type ProfileFields, profileFolder, type Rating, ratingLimits, type RatingSite, ratingSites, updateProfile, } from "./profiles.ts";
2
+ export { type Accounts, activateProfile, activeProfile, createProfile, type DrawnAvatar, drawnAvatars, ensureProfile, gameFolder, gameFolders, type Level, levels, listProfiles, nameLimit, type PlayerProfile, type ProfileFields, profileFolder, type Rating, ratingLimits, type RatingSite, ratingSites, updateProfile, } from "./profiles.ts";
3
3
  export { helpsIn, languageIn, type PageSettings, profileSettings, saveProfileSettings, settingsOf } from "./settings.ts";
@@ -26,12 +26,23 @@ export interface ProfileFields {
26
26
  readonly rating?: Rating;
27
27
  readonly avatar?: DrawnAvatar;
28
28
  }
29
+ /** The player's names on the sites they play on. */
30
+ export interface Accounts {
31
+ readonly lichess?: string;
32
+ readonly chesscom?: string;
33
+ }
29
34
  /** A profile: its folder's name, and what profile.md says; a folder without one is named by the folder. */
30
35
  export interface PlayerProfile extends ProfileFields {
31
36
  readonly id: string;
32
37
  /** Made by Chesspal at the first start, until the player completes it: the board page's wizard does (D-71). */
33
38
  readonly new?: true;
39
+ /** The player's names on Lichess and chess.com, written in profile.md by hand or from the page, by which the games they load are theirs (D-93). */
40
+ readonly accounts?: Accounts;
34
41
  }
42
+ /** What may change in a profile: what the player tells of themselves, and their names on the sites, each one written whole. */
43
+ export type ProfileChanges = Partial<ProfileFields> & {
44
+ readonly accounts?: Accounts;
45
+ };
35
46
  /** The profiles in the data folder, by their folders' names. */
36
47
  export declare function listProfiles(root: string): PlayerProfile[];
37
48
  /**
@@ -46,13 +57,13 @@ export declare function gameFolder(root: string): string;
46
57
  /** A profile's folder. */
47
58
  export declare function profileFolder(root: string, id: string): string;
48
59
  /**
49
- * Makes a profile, its folder named from the player's name, and puts it in use; the one Chesspal made, while new, becomes it instead, with its games, and
50
- * its folder takes the player's name too (D-71).
60
+ * Makes a profile, its folder named from the player's name, and puts it in use unless told not to; the one Chesspal made, while new, becomes it instead,
61
+ * with its games, and its folder takes the player's name too (D-71).
51
62
  */
52
- export declare function createProfile(root: string, fields: ProfileFields): PlayerProfile;
63
+ export declare function createProfile(root: string, fields: ProfileFields, use?: boolean): PlayerProfile;
53
64
  /** Every profile's folder, where games may be. */
54
65
  export declare function gameFolders(root: string): string[];
55
66
  /** Puts a profile in use. */
56
67
  export declare function activateProfile(root: string, id: string): PlayerProfile;
57
68
  /** Changes what a profile says of the player, keeping the rest of profile.md as it was written: its other fields and the text below them. */
58
- export declare function updateProfile(root: string, id: string, changes: Partial<ProfileFields>): PlayerProfile;
69
+ export declare function updateProfile(root: string, id: string, changes: ProfileChanges): PlayerProfile;
@@ -41,10 +41,10 @@ export function profileFolder(root, id) {
41
41
  return join(root, folderName, id);
42
42
  }
43
43
  /**
44
- * Makes a profile, its folder named from the player's name, and puts it in use; the one Chesspal made, while new, becomes it instead, with its games, and
45
- * its folder takes the player's name too (D-71).
44
+ * Makes a profile, its folder named from the player's name, and puts it in use unless told not to; the one Chesspal made, while new, becomes it instead,
45
+ * with its games, and its folder takes the player's name too (D-71).
46
46
  */
47
- export function createProfile(root, fields) {
47
+ export function createProfile(root, fields, use = true) {
48
48
  const id = activeId(root);
49
49
  if (id !== undefined && readProfile(root, id).new === true) {
50
50
  const { level, rating, avatar } = fields;
@@ -57,7 +57,7 @@ export function createProfile(root, fields) {
57
57
  while (!claimed(root, made)) {
58
58
  made = freeId(root, slugOf(fields.name));
59
59
  }
60
- return fillProfile(root, made, fields, false);
60
+ return fillProfile(root, made, fields, false, use);
61
61
  }
62
62
  /** Every profile's folder, where games may be. */
63
63
  export function gameFolders(root) {
@@ -117,11 +117,13 @@ function claimed(root, id) {
117
117
  throw new Error(`${folder} could not be made (${error.message}): check that it is a folder, and can be written.`, { cause: error });
118
118
  }
119
119
  }
120
- /** Writes a profile in the folder this call claimed and puts it in use; on a failure the folder goes, which holds nothing of another's. */
121
- function fillProfile(root, id, fields, made) {
120
+ /** Writes a profile in the folder this call claimed and puts it in use when asked; on a failure the folder goes, which holds nothing of another's. */
121
+ function fillProfile(root, id, fields, made, use = true) {
122
122
  try {
123
123
  writeProfile(root, id, made ? { ...fields, new: true } : fields);
124
- writeActive(root, id);
124
+ if (use) {
125
+ writeActive(root, id);
126
+ }
125
127
  }
126
128
  catch (error) {
127
129
  rmSync(profileFolder(root, id), { recursive: true, force: true });
@@ -183,8 +185,19 @@ function readProfile(root, id) {
183
185
  ...(rated ? { rating: { value, site } } : {}),
184
186
  ...(avatar === undefined ? {} : { avatar }),
185
187
  ...(fields.new === "true" ? { new: true } : {}),
188
+ ...accountsIn(fields),
186
189
  };
187
190
  }
191
+ /** The player's names on the sites, from profile.md's lichess and chesscom lines; none without either. */
192
+ function accountsIn(fields) {
193
+ const named = (site) => fields[site]?.trim().slice(0, nameLimit).trim() || undefined;
194
+ const lichess = named("lichess");
195
+ const chesscom = named("chesscom");
196
+ if (lichess === undefined && chesscom === undefined) {
197
+ return {};
198
+ }
199
+ return { accounts: { ...(lichess === undefined ? {} : { lichess }), ...(chesscom === undefined ? {} : { chesscom }) } };
200
+ }
188
201
  // Writes the fields that change, and keeps the others as the file has them, even those this version does not read.
189
202
  function writeProfile(root, id, changes) {
190
203
  const { fields, body } = readFrontMatter(profileText(root, id));
@@ -202,6 +215,10 @@ function writeProfile(root, id, changes) {
202
215
  if ("avatar" in changes) {
203
216
  written.avatar = changes.avatar;
204
217
  }
218
+ if ("accounts" in changes) {
219
+ written.lichess = changes.accounts?.lichess;
220
+ written.chesscom = changes.accounts?.chesscom;
221
+ }
205
222
  if ("new" in changes) {
206
223
  written.new = changes.new === true ? "true" : undefined;
207
224
  }
@@ -9,10 +9,18 @@ export declare class Bell {
9
9
  private listener;
10
10
  private pending;
11
11
  private busy;
12
+ private woken;
12
13
  private readonly news;
13
14
  private readonly changed;
14
- /** `news` says whether a ring no hook heard is still worth waking the chat for; `changed` is told when the chat starts or stops listening. */
15
+ /**
16
+ * `news` says whether a ring no hook heard is still worth waking the chat for; `changed` is told when what the page shows of the chat may change: it
17
+ * starts or stops listening, the doorbell wakes it, its turn ends.
18
+ */
15
19
  constructor(news?: () => boolean, changed?: () => void);
20
+ /** Whether the chat is in a turn, as far as the doorbell knows: a chat stopped with Esc stays in it until its next tool. */
21
+ get inTurn(): boolean;
22
+ /** Whether the doorbell woke the chat in the turn it is in. */
23
+ get woke(): boolean;
16
24
  /** Whether the chat hears the player: in a turn, or waiting for the bell with a hook. */
17
25
  get listening(): boolean;
18
26
  /** The chat calls a tool, so it is in a turn: true when nobody was listening before. */
@@ -33,7 +41,7 @@ export interface BellProbe {
33
41
  }
34
42
  /** The ports a folder's page tries first: one in each of a few ranges, by its path, since Windows may reserve a range. The hook looks for it there. */
35
43
  export declare function pagePorts(folder: string): number[];
36
- /** What Codex's Stop hook answers to wake the chat (T-62): its turn goes on with the doorbell's line. */
44
+ /** What Codex's Stop hook answers to wake the chat: its turn goes on with the doorbell's line. */
37
45
  export declare const codexBellAnswer: string;
38
46
  /**
39
47
  * Reads the hook's input, waits for the bell, and ends the process as the host reads it: for Claude Code's asyncRewake, 2 with the line on stderr; for
@@ -1,5 +1,5 @@
1
- // The doorbell for the page (D-47): `chesspal listen`, the Stop hook init writes for Claude Code and Codex (T-62), waits at the page of its chat and, when
2
- // the player moves or writes there (T-65), tells the chat to call status. It never carries the move, the words or the position: status reads them fresh.
1
+ // The doorbell for the page (D-47): `chesspal listen`, the Stop hook init writes for Claude Code and Codex, waits at the page of its chat and, when
2
+ // the player moves or writes there, tells the chat to call status. It never carries the move, the words or the position: status reads them fresh.
3
3
  import { createHash } from "node:crypto";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { get } from "node:http";
@@ -16,13 +16,26 @@ export class Bell {
16
16
  pending = false;
17
17
  // The chat is in a turn: it called a tool, or a hook woke it, and its turn has not ended yet. A chat never started, or just cleared, is not.
18
18
  busy = false;
19
+ // The doorbell woke the chat in this turn: the player acted on the page, and the chat goes to see it.
20
+ woken = false;
19
21
  news;
20
22
  changed;
21
- /** `news` says whether a ring no hook heard is still worth waking the chat for; `changed` is told when the chat starts or stops listening. */
23
+ /**
24
+ * `news` says whether a ring no hook heard is still worth waking the chat for; `changed` is told when what the page shows of the chat may change: it
25
+ * starts or stops listening, the doorbell wakes it, its turn ends.
26
+ */
22
27
  constructor(news = () => true, changed = () => undefined) {
23
28
  this.news = news;
24
29
  this.changed = changed;
25
30
  }
31
+ /** Whether the chat is in a turn, as far as the doorbell knows: a chat stopped with Esc stays in it until its next tool. */
32
+ get inTurn() {
33
+ return this.busy;
34
+ }
35
+ /** Whether the doorbell woke the chat in the turn it is in. */
36
+ get woke() {
37
+ return this.woken;
38
+ }
26
39
  /** Whether the chat hears the player: in a turn, or waiting for the bell with a hook. */
27
40
  get listening() {
28
41
  return this.busy || this.listener !== undefined;
@@ -43,23 +56,26 @@ export class Bell {
43
56
  this.listener.end("ring");
44
57
  this.listener = undefined;
45
58
  this.busy = true;
59
+ this.woken = true;
60
+ this.changed();
46
61
  }
47
62
  /** A hook waits for the bell, so the chat's turn has ended; the hook before it, of a turn already ended, is let go. */
48
63
  listen(request, response) {
49
64
  response.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
50
- const asleep = !this.listening;
51
65
  const missed = this.pending;
52
66
  this.pending = false;
53
67
  if (missed && this.news()) {
54
68
  response.end("ring");
55
69
  this.busy = true;
56
- this.tellIf(asleep);
70
+ this.woken = true;
71
+ this.changed();
57
72
  return;
58
73
  }
59
74
  this.busy = false;
75
+ this.woken = false;
60
76
  this.listener?.end("dismissed");
61
77
  this.listener = response;
62
- this.tellIf(asleep);
78
+ this.changed();
63
79
  request.on("close", () => {
64
80
  if (this.listener === response) {
65
81
  // The hook went without the bell (the chat stopped, cleared or closed): nobody hears the player until the chat calls a tool again.
@@ -83,7 +99,7 @@ export function pagePorts(folder) {
83
99
  const offset = createHash("sha256").update(resolve(folder).toLowerCase()).digest()[0] % 10;
84
100
  return [5870, 6870, 7870].map((base) => base + offset);
85
101
  }
86
- /** What Codex's Stop hook answers to wake the chat (T-62): its turn goes on with the doorbell's line. */
102
+ /** What Codex's Stop hook answers to wake the chat: its turn goes on with the doorbell's line. */
87
103
  export const codexBellAnswer = JSON.stringify({ decision: "block", reason: bellLine });
88
104
  /**
89
105
  * Reads the hook's input, waits for the bell, and ends the process as the host reads it: for Claude Code's asyncRewake, 2 with the line on stderr; for
@@ -95,7 +111,7 @@ export async function listen(codex = false) {
95
111
  input += String(chunk);
96
112
  }
97
113
  const heard = await bellHeard(input).catch((error) => ({ folder: chatFolder(hookInput(input)), rang: false, why: `failed: ${error.message}` }));
98
- // Codex's turn ends with a hook that heard no bell, and the player's next move on the page wakes nobody: the log keeps why (T-62).
114
+ // Codex's turn ends with a hook that heard no bell, and the player's next move on the page wakes nobody: the log keeps why.
99
115
  if (codex && !heard.rang) {
100
116
  noteSilence(heard.folder, heard.why);
101
117
  }
@@ -139,10 +155,6 @@ const probeWait = 1000;
139
155
  // The log of Codex's hooks that heard no bell, in the chat's folder: its last lines only.
140
156
  const silenceLog = "listen.log";
141
157
  const silenceLines = 100;
142
- /** The chat's folder, where its server started: Claude Code names it, and the hook's cwd follows the cd's of the chat. */
143
- function chatFolder(hook) {
144
- return resolve(process.env.CLAUDE_PROJECT_DIR ?? hook.cwd ?? process.cwd());
145
- }
146
158
  /** Adds a line to the log of the hooks that heard no bell, keeping its last lines; a log that cannot be written never stops the hook. */
147
159
  export function noteSilence(folder, why, now = new Date()) {
148
160
  const path = join(folder, silenceLog);
@@ -154,6 +166,10 @@ export function noteSilence(folder, why, now = new Date()) {
154
166
  // The log only explains; the hook ends the same.
155
167
  }
156
168
  }
169
+ /** The chat's folder, where its server started: Claude Code names it, and the hook's cwd follows the cd's of the chat. */
170
+ function chatFolder(hook) {
171
+ return resolve(process.env.CLAUDE_PROJECT_DIR ?? hook.cwd ?? process.cwd());
172
+ }
157
173
  function hookInput(input) {
158
174
  try {
159
175
  const parsed = JSON.parse(input);
@@ -1,4 +1,4 @@
1
- import type { Caller } from "../commands/context.ts";
1
+ import type { Caller } from "../commands/index.ts";
2
2
  /** The host as it names itself to the server. */
3
3
  export interface Client {
4
4
  name: string;
@@ -6,5 +6,16 @@ export interface Client {
6
6
  }
7
7
  /** Who made the call with these metadata: the model and effort the host ties to it, else the host's name; undefined without a host. */
8
8
  export declare function callerOf(meta: Record<string, unknown> | undefined, client: Client | undefined, folder: string, wait?: number): Promise<Caller | undefined>;
9
- /** The model and effort on the transcript's line of a tool call, in the chat's file or its subagents'; Claude Code writes it a few milliseconds late. */
10
- export declare function lineOfCall(transcript: string, call: string, wait?: number): Promise<Caller | undefined>;
9
+ /** Who in Claude Code made a tool call: the chat itself, or one of its agents by its type. */
10
+ export type CallingAgent = {
11
+ chat: true;
12
+ } | {
13
+ agent: string;
14
+ };
15
+ /**
16
+ * Who in Claude Code made a tool call: the chat, when its line is in the chat's transcript, or the agent whose file has it, by the type Claude Code notes
17
+ * beside that file (Priya). Undefined when it cannot be told: another host, no chat noted, no line, or no type.
18
+ */
19
+ export declare function callingAgent(meta: Record<string, unknown> | undefined, folder: string, wait?: number): Promise<CallingAgent | undefined>;
20
+ /** Whether an agent's tool may answer this caller: one of its agents, or a caller that cannot be told, since the tool's description is the first guard. */
21
+ export declare function allowedCaller(caller: CallingAgent | undefined, agents: readonly string[]): boolean;
@@ -1,6 +1,6 @@
1
- // Who makes a tool call (D-81): only what the host ties to that call, never what the AI says of itself nor a setting the chat may have changed (T-113).
1
+ // Who makes a tool call (D-81): only what the host ties to that call, never what the AI says of itself nor a setting the chat may have changed.
2
2
  // Codex sends the model and the effort with each call; Claude Code writes them into the chat's transcript, on the line of the call.
3
- import { closeSync, openSync, readdirSync, readSync, statSync } from "node:fs";
3
+ import { closeSync, openSync, readdirSync, readFileSync, readSync, statSync } from "node:fs";
4
4
  import { basename, dirname, join } from "node:path";
5
5
  import { lastChatStart } from "../games/index.js";
6
6
  /** Who made the call with these metadata: the model and effort the host ties to it, else the host's name; undefined without a host. */
@@ -19,13 +19,45 @@ export async function callerOf(meta, client, folder, wait = 50) {
19
19
  }
20
20
  return client === undefined ? undefined : { annotator: client.title ?? client.name, hostOnly: true };
21
21
  }
22
+ /**
23
+ * Who in Claude Code made a tool call: the chat, when its line is in the chat's transcript, or the agent whose file has it, by the type Claude Code notes
24
+ * beside that file (Priya). Undefined when it cannot be told: another host, no chat noted, no line, or no type.
25
+ */
26
+ export async function callingAgent(meta, folder, wait = 50) {
27
+ const call = meta?.["claudecode/toolUseId"];
28
+ const transcript = lastChatStart(folder)?.transcript;
29
+ const found = typeof call === "string" && transcript !== undefined ? await foundCall(transcript, call, wait) : undefined;
30
+ if (found === undefined) {
31
+ return undefined;
32
+ }
33
+ if (found.file === transcript) {
34
+ return { chat: true };
35
+ }
36
+ const type = objectOf(textOf(found.file.replace(/\.jsonl$/, ".meta.json")))?.agentType;
37
+ return typeof type === "string" ? { agent: type } : undefined;
38
+ }
39
+ /** Whether an agent's tool may answer this caller: one of its agents, or a caller that cannot be told, since the tool's description is the first guard. */
40
+ export function allowedCaller(caller, agents) {
41
+ return caller === undefined || ("agent" in caller && agents.includes(caller.agent));
42
+ }
22
43
  /** The model and effort on the transcript's line of a tool call, in the chat's file or its subagents'; Claude Code writes it a few milliseconds late. */
23
- export async function lineOfCall(transcript, call, wait = 50) {
44
+ async function lineOfCall(transcript, call, wait = 50) {
45
+ return (await foundCall(transcript, call, wait))?.caller;
46
+ }
47
+ // Three seconds at most: the line comes 30 or 40 milliseconds after a short call, but over 300 after annotate_game's long one. Nobody waits for it on the
48
+ // AI's clock: annotate_game runs with no game going on, and the AI's move is read after its answer.
49
+ const attempts = 60;
50
+ // The line of a call just made is near the end of its file.
51
+ const tailBytes = 512 * 1024;
52
+ // A subagent's file is one written in the last minute.
53
+ const recentFiles = 60_000;
54
+ /** Who made a tool call, and the file its line is in, the chat's own or a subagent's; none when no line has it. */
55
+ async function foundCall(transcript, call, wait) {
24
56
  for (let attempt = 0; attempt < attempts; attempt++) {
25
57
  for (const file of [transcript, ...subagentFiles(transcript)]) {
26
58
  const found = callerIn(tailOf(file), call);
27
59
  if (found !== undefined) {
28
- return found;
60
+ return { caller: found, file };
29
61
  }
30
62
  }
31
63
  if (attempt < attempts - 1) {
@@ -34,13 +66,6 @@ export async function lineOfCall(transcript, call, wait = 50) {
34
66
  }
35
67
  return undefined;
36
68
  }
37
- // Three seconds at most: the line comes 30 or 40 milliseconds after a short call, but over 300 after annotate_game's long one. Nobody waits for it on the
38
- // AI's clock: annotate_game runs with no game going on, and the AI's move is read after its answer.
39
- const attempts = 60;
40
- // The line of a call just made is near the end of its file.
41
- const tailBytes = 512 * 1024;
42
- // A subagent's file is one written in the last minute.
43
- const recentFiles = 60_000;
44
69
  function callerIn(text, call) {
45
70
  const marker = `"id":"${call}"`;
46
71
  for (const line of text.split("\n").reverse()) {
@@ -86,6 +111,14 @@ function tailOf(file) {
86
111
  return "";
87
112
  }
88
113
  }
114
+ function textOf(file) {
115
+ try {
116
+ return readFileSync(file, "utf8");
117
+ }
118
+ catch {
119
+ return "";
120
+ }
121
+ }
89
122
  function objectOf(value) {
90
123
  if (typeof value === "string") {
91
124
  try {
@@ -1,6 +1,5 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { type Context } from "../commands/context.ts";
3
- import { type AnyCommand } from "../commands/registry.ts";
2
+ import { type AnyCommand, type Context } from "../commands/index.ts";
4
3
  /** What the host reads as the server starts, before any tool: where to begin, and the rule of every move (D-27). Short, since hosts may cut it. */
5
4
  export declare const serverInstructions: string;
6
5
  /** The MCP server for an AI host: every command of the registry as a tool, with its description and parameters (D-5). */