chesspal 0.2.4 → 0.3.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 (183) hide show
  1. package/README.md +11 -12
  2. package/dist/cli/cli.js +83 -3
  3. package/dist/cli/command-line.js +26 -9
  4. package/dist/cli/icon.d.ts +25 -0
  5. package/dist/cli/icon.js +80 -0
  6. package/dist/commands/agents.d.ts +19 -2
  7. package/dist/commands/agents.js +39 -13
  8. package/dist/commands/ai-say.js +3 -4
  9. package/dist/commands/analyse-game.d.ts +4 -0
  10. package/dist/commands/analyse-game.js +46 -13
  11. package/dist/commands/annotate-game.d.ts +2 -0
  12. package/dist/commands/annotate-game.js +40 -18
  13. package/dist/commands/answer-lines.d.ts +2 -0
  14. package/dist/commands/answer-lines.js +13 -3
  15. package/dist/commands/coach.js +4 -2
  16. package/dist/commands/context.d.ts +9 -1
  17. package/dist/commands/context.js +5 -0
  18. package/dist/commands/delete-review.js +9 -5
  19. package/dist/commands/game-summary.d.ts +2 -0
  20. package/dist/commands/game-summary.js +4 -0
  21. package/dist/commands/index.d.ts +3 -2
  22. package/dist/commands/index.js +7 -4
  23. package/dist/commands/init.js +2 -4
  24. package/dist/commands/instructions.d.ts +9 -0
  25. package/dist/commands/instructions.js +30 -0
  26. package/dist/commands/lead-input.d.ts +1 -1
  27. package/dist/commands/lead-input.js +7 -4
  28. package/dist/commands/page-link.js +6 -5
  29. package/dist/commands/past-games.d.ts +1 -1
  30. package/dist/commands/past-games.js +12 -5
  31. package/dist/commands/player-move.js +2 -3
  32. package/dist/commands/profiles.d.ts +23 -9
  33. package/dist/commands/profiles.js +76 -35
  34. package/dist/commands/ratings.d.ts +8 -0
  35. package/dist/commands/ratings.js +45 -0
  36. package/dist/commands/registry.d.ts +1 -1
  37. package/dist/commands/registry.js +3 -2
  38. package/dist/commands/say.d.ts +1 -1
  39. package/dist/commands/say.js +1 -1
  40. package/dist/commands/status.d.ts +3 -1
  41. package/dist/commands/status.js +46 -27
  42. package/dist/commands/study.js +8 -5
  43. package/dist/core/index.d.ts +1 -1
  44. package/dist/core/index.js +1 -1
  45. package/dist/core/review.d.ts +2 -0
  46. package/dist/core/review.js +5 -1
  47. package/dist/games/ai-model.d.ts +4 -2
  48. package/dist/games/ai-model.js +10 -1
  49. package/dist/games/analysis.d.ts +26 -3
  50. package/dist/games/analysis.js +41 -6
  51. package/dist/games/archive.d.ts +6 -3
  52. package/dist/games/archive.js +13 -5
  53. package/dist/games/current-game.d.ts +10 -1
  54. package/dist/games/current-game.js +49 -15
  55. package/dist/games/files.d.ts +5 -0
  56. package/dist/games/files.js +42 -1
  57. package/dist/games/index.d.ts +3 -3
  58. package/dist/games/index.js +3 -3
  59. package/dist/home/data-folder.d.ts +2 -0
  60. package/dist/home/data-folder.js +5 -1
  61. package/dist/home/index.d.ts +1 -1
  62. package/dist/home/index.js +1 -1
  63. package/dist/host-files/agents.d.ts +2 -2
  64. package/dist/host-files/agents.js +12 -6
  65. package/dist/host-files/host-files.d.ts +5 -3
  66. package/dist/host-files/host-files.js +15 -8
  67. package/dist/host-files/index.d.ts +2 -2
  68. package/dist/host-files/index.js +2 -2
  69. package/dist/page/app/app.js +29 -16
  70. package/dist/page/app/title.js +17 -0
  71. package/dist/page/avatars/torretta.js +4 -4
  72. package/dist/page/dialogs/analyses.js +4 -4
  73. package/dist/page/dialogs/dialog.js +4 -1
  74. package/dist/page/dialogs/dialogs.css +84 -10
  75. package/dist/page/dialogs/profiles.js +95 -42
  76. package/dist/page/game/column.js +5 -1
  77. package/dist/page/game/plies.js +7 -4
  78. package/dist/page/guide/blocks.js +67 -0
  79. package/dist/page/guide/en.md +66 -0
  80. package/dist/page/guide/foot.js +18 -0
  81. package/dist/page/guide/guide.css +200 -0
  82. package/dist/page/guide/guide.js +89 -0
  83. package/dist/page/guide/it.md +66 -0
  84. package/dist/page/guide/markdown.js +67 -0
  85. package/dist/page/helps/helps.css +7 -10
  86. package/dist/page/layout/favicon.svg +13 -0
  87. package/dist/page/layout/header.js +3 -3
  88. package/dist/page/layout/icon.icns +0 -0
  89. package/dist/page/layout/icon.ico +0 -0
  90. package/dist/page/layout/icon.png +0 -0
  91. package/dist/page/layout/layout.css +111 -4
  92. package/dist/page/layout/window.js +45 -0
  93. package/dist/page/main.js +4 -1
  94. package/dist/page/players/card.js +1 -1
  95. package/dist/page/profile/profile.js +14 -9
  96. package/dist/page/profile/settings.js +9 -0
  97. package/dist/page/style.css +2 -0
  98. package/dist/page/table/opener.css +65 -0
  99. package/dist/page/table/opener.js +59 -0
  100. package/dist/page/talk/chat.css +10 -3
  101. package/dist/page/talk/talk.css +38 -26
  102. package/dist/page/talk/talk.js +4 -2
  103. package/dist/page/text.js +83 -17
  104. package/dist/page/theme.css +7 -0
  105. package/dist/profiles/index.d.ts +2 -1
  106. package/dist/profiles/index.js +2 -1
  107. package/dist/profiles/profiles.d.ts +10 -15
  108. package/dist/profiles/profiles.js +30 -15
  109. package/dist/profiles/ratings.d.ts +35 -0
  110. package/dist/profiles/ratings.js +61 -0
  111. package/dist/server/bell.d.ts +24 -3
  112. package/dist/server/bell.js +84 -10
  113. package/dist/server/caller.d.ts +2 -1
  114. package/dist/server/caller.js +3 -2
  115. package/dist/server/mcp.d.ts +7 -3
  116. package/dist/server/mcp.js +100 -52
  117. package/dist/server/page/analyses.d.ts +2 -0
  118. package/dist/server/page/analyses.js +20 -5
  119. package/dist/server/page/attach.d.ts +4 -0
  120. package/dist/server/page/attach.js +112 -0
  121. package/dist/server/page/chat.d.ts +60 -0
  122. package/dist/server/page/chat.js +79 -0
  123. package/dist/server/page/events.d.ts +7 -0
  124. package/dist/server/page/events.js +20 -0
  125. package/dist/server/page/files.js +10 -1
  126. package/dist/server/page/game.js +43 -23
  127. package/dist/server/page/page.d.ts +47 -7
  128. package/dist/server/page/page.js +69 -48
  129. package/dist/server/page/profiles.d.ts +5 -0
  130. package/dist/server/page/profiles.js +26 -10
  131. package/dist/server/page/table.d.ts +39 -0
  132. package/dist/server/page/table.js +102 -0
  133. package/dist/server/table/browser.d.ts +6 -0
  134. package/dist/server/table/browser.js +48 -0
  135. package/dist/server/table/hosts/chats.d.ts +7 -0
  136. package/dist/server/table/hosts/chats.js +31 -0
  137. package/dist/server/table/hosts/find.d.ts +2 -0
  138. package/dist/server/table/hosts/find.js +20 -0
  139. package/dist/server/table/hosts/hosts.d.ts +34 -0
  140. package/dist/server/table/hosts/hosts.js +49 -0
  141. package/dist/server/table/hosts/index.d.ts +3 -0
  142. package/dist/server/table/hosts/index.js +4 -0
  143. package/dist/server/table/hosts/play.d.ts +25 -0
  144. package/dist/server/table/hosts/play.js +152 -0
  145. package/dist/server/table/hosts/seat.d.ts +25 -0
  146. package/dist/server/table/hosts/seat.js +74 -0
  147. package/dist/server/table/hosts/terminal.d.ts +6 -0
  148. package/dist/server/table/hosts/terminal.js +63 -0
  149. package/dist/server/table/icon/index.d.ts +32 -0
  150. package/dist/server/table/icon/index.js +83 -0
  151. package/dist/server/table/icon/linux.d.ts +5 -0
  152. package/dist/server/table/icon/linux.js +47 -0
  153. package/dist/server/table/icon/macos.d.ts +3 -0
  154. package/dist/server/table/icon/macos.js +38 -0
  155. package/dist/server/table/icon/windows.d.ts +3 -0
  156. package/dist/server/table/icon/windows.js +48 -0
  157. package/dist/server/table/index.d.ts +6 -0
  158. package/dist/server/table/index.js +7 -0
  159. package/dist/server/table/launch.d.ts +19 -0
  160. package/dist/server/table/launch.js +78 -0
  161. package/dist/server/table/link.d.ts +10 -0
  162. package/dist/server/table/link.js +230 -0
  163. package/dist/server/table/table.d.ts +35 -0
  164. package/dist/server/table/table.js +94 -0
  165. package/dist/sites/chesscom.d.ts +4 -0
  166. package/dist/sites/chesscom.js +25 -0
  167. package/dist/sites/choice.d.ts +17 -0
  168. package/dist/sites/choice.js +13 -0
  169. package/dist/sites/index.d.ts +2 -0
  170. package/dist/sites/index.js +4 -0
  171. package/dist/sites/lichess.d.ts +4 -0
  172. package/dist/sites/lichess.js +32 -0
  173. package/dist/sites/request.d.ts +11 -0
  174. package/dist/sites/request.js +15 -0
  175. package/dist/sites/sites.d.ts +6 -0
  176. package/dist/sites/sites.js +33 -0
  177. package/instructions/analysis.md +1 -1
  178. package/instructions/analyst-agent.md +1 -1
  179. package/instructions/coach.md +2 -1
  180. package/instructions/opponent.md +6 -6
  181. package/instructions/ponder.md +1 -0
  182. package/instructions/start.md +1 -0
  183. package/package.json +1 -1
package/dist/page/text.js CHANGED
@@ -108,6 +108,7 @@ const english = {
108
108
  talk: "The talk",
109
109
  move: "Move",
110
110
  draw: "draw",
111
+ aborted: "Game aborted",
111
112
  checkmate: "checkmate",
112
113
  ended: {
113
114
  checkmate: "checkmate",
@@ -132,6 +133,18 @@ const english = {
132
133
  thinking: { move: "Thinking about its move", ponder: "Thinking while you play" },
133
134
  asleep: (name) => `${name} is not listening: write a message, such as "hi", in the window where you opened ${name}, not here. Its clock waits until it answers.`,
134
135
  asleepInAnalysis: (name) => `${name} is not listening: write to it in the window where you opened it, not here.`,
136
+ table: {
137
+ label: "Open a chat",
138
+ noChat: (name) => `To play with ${name}, open its chat: it starts in a window of its own, to keep open.`,
139
+ install: (name) => `To play with ${name}, install Claude Code or Codex, then open this page again.`,
140
+ model: "Model",
141
+ effort: "Effort",
142
+ asHost: (host) => `as set in ${host}`,
143
+ opening: "Opening the chat… In the terminal, if it asks whether you trust the folder, choose Yes with the arrow keys and press Enter.",
144
+ again: (name) => `${name} starts again with a clean chat for the new game…`,
145
+ failed: "The terminal did not open: type the command below in a terminal.",
146
+ typeIt: "Or type in a terminal:",
147
+ },
135
148
  commandHere: (name) => `/clear and the other commands go in the window where you opened ${name}, not here.`,
136
149
  refused: {
137
150
  "no-game": () => "There is no game going on: press + to start one, or ask for one in the chat.",
@@ -187,14 +200,15 @@ const english = {
187
200
  learning: ["I've just learned", "I know the rules, little more"],
188
201
  casual: ["I play now and then", "With friends or on my phone"],
189
202
  regular: ["I play often", "Online or at a club"],
190
- tournament: ["I play tournaments", "I have a FIDE rating"],
203
+ tournament: ["I play tournaments", "At a club, with a rating"],
191
204
  },
192
- rating: "Do you have a rating?",
193
205
  optional: "optional",
194
- ratingPlaceholder: "e.g. 1450",
195
- ratingLabel: "Rating",
196
- siteLabel: "Where",
197
- sites: { "lichess": "on Lichess", "chess.com": "on Chess.com", "fide": "FIDE" },
206
+ privacy: "We only read your public rating. No password.",
207
+ speeds: { rapid: "rapid", classical: "classical", blitz: "blitz" },
208
+ ratingTitle: (speed, played) => (played === undefined ? speed : `${speed} · last game ${played}`),
209
+ notFound: (site) => `${site} has no player of this name`,
210
+ unreached: (site) => `${site} isn't answering: we'll try again later`,
211
+ pending: "Reading your rating…",
198
212
  start: "Start playing",
199
213
  create: "Done",
200
214
  newProfile: "Add a player",
@@ -202,12 +216,17 @@ const english = {
202
216
  eachOwn: "Each player has their own games and settings.",
203
217
  changePlayer: "Change player",
204
218
  back: "Back",
205
- changePicture: "Change picture",
206
- yourPicture: "Your picture: change it",
219
+ yourProfile: "Your profile",
220
+ editProfile: "Edit your profile",
221
+ yourPicture: "Your picture: edit your profile",
207
222
  avatars: { "short-hair": "Short hair", bun: "Bun", glasses: "Glasses", beanie: "Beanie", beard: "Beard", curls: "Curls", moustache: "Moustache", "silver-bob": "Silver bob", cat: "Cat", owl: "Owl" },
208
223
  whilePlaying: "Finish the game, or resign, before changing player.",
224
+ accounts: "Do you play on Lichess, Chess.com or in FIDE tournaments?",
209
225
  username: "your username",
210
226
  accountTitle: "With your name there, a game you load from there knows which side is yours.",
227
+ fidePlaceholder: "your rating",
228
+ fideTitle: "Your FIDE rating, as FIDE lists it: Chesspal cannot read it by itself.",
229
+ fideRange: (lowest, highest) => `A FIDE rating goes from ${lowest} to ${highest}`,
211
230
  badImage: "That picture won't open: try another photo (the ones from a phone usually work).",
212
231
  },
213
232
  start: {
@@ -343,6 +362,20 @@ const english = {
343
362
  control: "How many of White's pieces and of Black's reach the square",
344
363
  ends: { crown: "Won", mate: "Checkmated", flag: "Resigned", half: "Draw, half a point each" },
345
364
  },
365
+ guide: {
366
+ title: "Guide",
367
+ contents: "Contents",
368
+ onGitHub: "Chesspal on GitHub",
369
+ onNpm: "Chesspal on npm",
370
+ support: "Support Chesspal",
371
+ credits: "Credits",
372
+ creditLines: [
373
+ "The rules: [chess.js](https://github.com/jhlywa/chess.js), under the BSD 2-Clause license.",
374
+ "The openings' names: Lichess's [chess-openings](https://github.com/lichess-org/chess-openings), under CC0.",
375
+ "The pieces: Nitida, Torretta and Capablanca are Chesspal's own, the last after the diagrams of Capablanca's *Chess Fundamentals* (1921), under CC BY 4.0: [their credits](https://github.com/stefanocaronia/chesspal/blob/main/src/page/pieces/CREDITS.md).",
376
+ "The sounds: Chesspal's own.",
377
+ ],
378
+ },
346
379
  };
347
380
  const italian = {
348
381
  language: "it",
@@ -447,6 +480,7 @@ const italian = {
447
480
  talk: "Conversazione",
448
481
  move: "Mossa",
449
482
  draw: "patta",
483
+ aborted: "Partita annullata",
450
484
  checkmate: "scacco matto",
451
485
  ended: {
452
486
  checkmate: "scacco matto",
@@ -471,6 +505,18 @@ const italian = {
471
505
  thinking: { move: "Sta pensando alla sua mossa", ponder: "Pensa mentre giochi tu" },
472
506
  asleep: (name) => `${name} non ti sta ascoltando: scrivi un messaggio, per esempio "ciao", nella finestra dove hai aperto ${name}, non qui. Il suo orologio aspetta che risponda.`,
473
507
  asleepInAnalysis: (name) => `${name} non ti sta ascoltando: scrivigli nella finestra dove l'hai aperto, non qui.`,
508
+ table: {
509
+ label: "Apri una chat",
510
+ noChat: (name) => `Per giocare con ${name}, apri la sua chat: parte in una finestra a parte, da tenere aperta.`,
511
+ install: (name) => `Per giocare con ${name}, installa Claude Code o Codex, poi apri di nuovo questa pagina.`,
512
+ model: "Modello",
513
+ effort: "Ragionamento",
514
+ asHost: (host) => `come è impostato in ${host}`,
515
+ opening: "Apro la chat… Nel terminale, se ti chiede se ti fidi della cartella, scegli Yes con le frecce e premi Invio.",
516
+ again: (name) => `${name} riparte con una chat pulita per la nuova partita…`,
517
+ failed: "Il terminale non si è aperto: scrivi il comando qui sotto in un terminale.",
518
+ typeIt: "Oppure scrivi in un terminale:",
519
+ },
474
520
  commandHere: (name) => `/clear e gli altri comandi vanno scritti nella finestra dove hai aperto ${name}, non qui.`,
475
521
  refused: {
476
522
  "no-game": () => "Nessuna partita in corso: premi + per iniziarne una, o chiedila nella chat.",
@@ -492,7 +538,7 @@ const italian = {
492
538
  piece: (letter, side) => `${italianNames[letter]} ${side === "white" ? (letter === "Q" || letter === "R" ? "bianca" : "bianco") : (letter === "Q" || letter === "R" ? "nera" : "nero")}`,
493
539
  taken: (pieces, ahead) => `Pezzi presi: ${pieces.length === 0 ? "nessuno" : pieces.join(", ")}${ahead > 0 ? `; avanti di ${ahead}` : ""}`,
494
540
  writeTo: (name) => `Scrivi a ${name}…`,
495
- askAbout: (name) => `Chiedi a ${name} di questa partita…`,
541
+ askAbout: (name) => `Chiedi ${name === "AI" ? "all'AI" : `a ${name}`} di questa partita…`,
496
542
  goTo: (move) => `Vai a ${move}`,
497
543
  readBy: (name) => `Letto da ${name}`,
498
544
  wrote: (name) => `${name} ha scritto`,
@@ -527,14 +573,15 @@ const italian = {
527
573
  learning: ["Ho appena imparato", "Conosco le regole, poco di più"],
528
574
  casual: ["Gioco ogni tanto", "Con amici o sul telefono"],
529
575
  regular: ["Gioco spesso", "Online o al circolo"],
530
- tournament: ["Gioco a torneo", "Ho un punteggio FIDE"],
576
+ tournament: ["Gioco a torneo", "Al circolo, con punteggio"],
531
577
  },
532
- rating: "Hai un punteggio?",
533
578
  optional: "facoltativo",
534
- ratingPlaceholder: "es. 1450",
535
- ratingLabel: "Punteggio",
536
- siteLabel: "Dove",
537
- sites: { "lichess": "su Lichess", "chess.com": "su Chess.com", "fide": "FIDE" },
579
+ privacy: "Leggiamo solo il tuo punteggio pubblico. Niente password.",
580
+ speeds: { rapid: "rapid", classical: "classica", blitz: "blitz" },
581
+ ratingTitle: (speed, played) => (played === undefined ? speed : `${speed} · ultima partita il ${played.split("-").reverse().join("/")}`),
582
+ notFound: (site) => `Su ${site} non trovo questo nome`,
583
+ unreached: (site) => `${site} non risponde: riproviamo più tardi`,
584
+ pending: "Leggo il tuo punteggio…",
538
585
  start: "Inizia a giocare",
539
586
  create: "Fatto",
540
587
  newProfile: "Aggiungi un giocatore",
@@ -542,12 +589,17 @@ const italian = {
542
589
  eachOwn: "Ognuno ha le sue partite e le sue impostazioni.",
543
590
  changePlayer: "Cambia giocatore",
544
591
  back: "Indietro",
545
- changePicture: "Cambia immagine",
546
- yourPicture: "La tua immagine: cambiala",
592
+ yourProfile: "Il tuo profilo",
593
+ editProfile: "Modifica il profilo",
594
+ yourPicture: "La tua immagine: modifica il profilo",
547
595
  avatars: { "short-hair": "Capelli corti", bun: "Chignon", glasses: "Occhiali", beanie: "Berretto", beard: "Barba", curls: "Ricci", moustache: "Baffi", "silver-bob": "Caschetto grigio", cat: "Gatto", owl: "Gufo" },
548
596
  whilePlaying: "Finisci la partita, o abbandona, prima di cambiare giocatore.",
597
+ accounts: "Giochi su Lichess, Chess.com o nei tornei FIDE?",
549
598
  username: "il tuo nome utente",
550
599
  accountTitle: "Col tuo nome lì, una partita che carichi da lì sa qual è la tua parte.",
600
+ fidePlaceholder: "il tuo punteggio",
601
+ fideTitle: "Il tuo punteggio FIDE, come lo trovi sul sito della FIDE: Chesspal non può leggerlo da sé.",
602
+ fideRange: (lowest, highest) => `Un punteggio FIDE va da ${lowest} a ${highest}`,
551
603
  badImage: "Questa immagine non si apre: prova con un'altra foto (quelle del telefono di solito vanno bene).",
552
604
  },
553
605
  start: {
@@ -683,6 +735,20 @@ const italian = {
683
735
  control: "Quanti pezzi del Bianco e del Nero raggiungono la casa",
684
736
  ends: { crown: "Ha vinto", mate: "Scacco matto", flag: "Ha abbandonato", half: "Patta, mezzo punto a testa" },
685
737
  },
738
+ guide: {
739
+ title: "Guida",
740
+ contents: "Indice",
741
+ onGitHub: "Chesspal su GitHub",
742
+ onNpm: "Chesspal su npm",
743
+ support: "Sostieni Chesspal",
744
+ credits: "Crediti",
745
+ creditLines: [
746
+ "Le regole: [chess.js](https://github.com/jhlywa/chess.js), con licenza BSD 2-Clause.",
747
+ "I nomi delle aperture: [chess-openings](https://github.com/lichess-org/chess-openings) di Lichess, con licenza CC0.",
748
+ "I pezzi: Nitida, Torretta e Capablanca sono di Chesspal, l'ultimo dai diagrammi di *Chess Fundamentals* di Capablanca (1921), con licenza CC BY 4.0: [i loro crediti](https://github.com/stefanocaronia/chesspal/blob/main/src/page/pieces/CREDITS.md).",
749
+ "I suoni: di Chesspal.",
750
+ ],
751
+ },
686
752
  };
687
753
  // How each piece moves, said when it was taken where it cannot go (Anatolij's words).
688
754
  const englishMoves = {
@@ -33,6 +33,10 @@
33
33
  --help-on: #fff;
34
34
  --help-line: rgba(17, 22, 29, 0.78);
35
35
  --help-halo: rgba(255, 255, 255, 0.85);
36
+ /* The control map's veils (Zoe's variant A): grey over the board, sand where White alone reaches, slate where Black alone does */
37
+ --map-veil: rgba(160, 166, 176, 0.5);
38
+ --map-white: rgba(242, 233, 210, 0.78);
39
+ --map-black: rgba(56, 66, 84, 0.5);
36
40
  --torretta-body: #6d9fd1;
37
41
  --torretta-shade: #4b7cb0;
38
42
  --torretta-screen: #16263a;
@@ -102,6 +106,9 @@
102
106
  --badge-blunder: #d93434;
103
107
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 22px rgba(0, 0, 0, 0.25);
104
108
  --board-shadow: var(--shadow);
109
+ --map-veil: rgba(112, 120, 132, 0.66);
110
+ --map-white: rgba(226, 214, 188, 0.64);
111
+ --map-black: rgba(28, 35, 48, 0.7);
105
112
  color-scheme: dark;
106
113
  }
107
114
 
@@ -1,3 +1,4 @@
1
1
  export { type ImageType, imageTypeOf, imageTypes, ownImage, ownImageStamp, removeOwnImage, saveOwnImage, sentImageLimit } from "./avatar.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";
2
+ export { type Accounts, activateProfile, activeProfile, createProfile, type DrawnAvatar, drawnAvatars, ensureProfile, fideHighest, fideLowest, gameFolder, gameFolders, type Level, levels, listProfiles, nameLimit, type PlayerProfile, type ProfileFields, profileFolder, updateProfile, } from "./profiles.ts";
3
+ export { type Rating, type RatingSite, ratingSites, saveReadings, type SiteReading, type SiteReadings, type Speed, speeds } from "./ratings.ts";
3
4
  export { helpsIn, languageIn, type PageSettings, profileSettings, saveProfileSettings, settingsOf } from "./settings.ts";
@@ -1,5 +1,6 @@
1
1
  // The player's profiles (D-23): who plays, their games, their picture and their page settings, each in profiles/<id>/ of the data folder (D-71). Below the
2
2
  // commands, beside the games.
3
3
  export { imageTypeOf, imageTypes, ownImage, ownImageStamp, removeOwnImage, saveOwnImage, sentImageLimit } from "./avatar.js";
4
- export { activateProfile, activeProfile, createProfile, drawnAvatars, ensureProfile, gameFolder, gameFolders, levels, listProfiles, nameLimit, profileFolder, ratingLimits, ratingSites, updateProfile, } from "./profiles.js";
4
+ export { activateProfile, activeProfile, createProfile, drawnAvatars, ensureProfile, fideHighest, fideLowest, gameFolder, gameFolders, levels, listProfiles, nameLimit, profileFolder, updateProfile, } from "./profiles.js";
5
+ export { ratingSites, saveReadings, speeds } from "./ratings.js";
5
6
  export { helpsIn, languageIn, profileSettings, saveProfileSettings, settingsOf } from "./settings.js";
@@ -1,30 +1,21 @@
1
+ import { type Rating, type SiteReadings } from "./ratings.ts";
1
2
  /** How the player says they play, from who has just learned to who plays tournaments. */
2
3
  export declare const levels: readonly ["learning", "casual", "regular", "tournament"];
3
4
  export type Level = (typeof levels)[number];
4
- /** Where a rating comes from. */
5
- export declare const ratingSites: readonly ["lichess", "chess.com", "fide"];
6
- export type RatingSite = (typeof ratingSites)[number];
7
- /** The lowest and highest rating a profile takes. */
8
- export declare const ratingLimits: {
9
- readonly lowest: 100;
10
- readonly highest: 3500;
11
- };
12
5
  /** The longest name a profile keeps. */
13
6
  export declare const nameLimit = 40;
14
7
  /** The avatars Zoe drew, for a player without an image of their own (D-45). */
15
8
  export declare const drawnAvatars: readonly ["short-hair", "bun", "glasses", "beanie", "beard", "curls", "moustache", "silver-bob", "cat", "owl"];
16
9
  export type DrawnAvatar = (typeof drawnAvatars)[number];
17
- /** A rating and where it comes from. */
18
- export interface Rating {
19
- readonly value: number;
20
- readonly site: RatingSite;
21
- }
22
- /** What the player tells of themselves: their name, how they play, their rating, the avatar they chose. */
10
+ /** The FIDE ratings Chesspal takes: whole numbers from FIDE's lowest, 1400 since 2024, to 3000. */
11
+ export declare const fideLowest = 1400;
12
+ export declare const fideHighest = 3000;
13
+ /** What the player tells of themselves: their name, how they play, the avatar they chose, their FIDE rating, typed since FIDE has no API (D-106). */
23
14
  export interface ProfileFields {
24
15
  readonly name: string;
25
16
  readonly level?: Level;
26
- readonly rating?: Rating;
27
17
  readonly avatar?: DrawnAvatar;
18
+ readonly fide?: number;
28
19
  }
29
20
  /** The player's names on the sites they play on. */
30
21
  export interface Accounts {
@@ -38,6 +29,10 @@ export interface PlayerProfile extends ProfileFields {
38
29
  readonly new?: true;
39
30
  /** 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
31
  readonly accounts?: Accounts;
32
+ /** What Lichess and chess.com said of those names, the last time Chesspal asked (D-105). */
33
+ readonly sites?: SiteReadings;
34
+ /** The rating that counts, of the names the profile has now: of two, the one the sites are surer of (D-106). */
35
+ readonly rating?: Rating;
41
36
  }
42
37
  /** What may change in a profile: what the player tells of themselves, and their names on the sites, each one written whole. */
43
38
  export type ProfileChanges = Partial<ProfileFields> & {
@@ -4,16 +4,16 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:f
4
4
  import { join } from "node:path";
5
5
  import { makeFolder, renameOver, writeAtomically } from "../games/index.js";
6
6
  import { readFrontMatter, writeFrontMatter } from "./front-matter.js";
7
+ import { savedReadings } from "./ratings.js";
7
8
  /** How the player says they play, from who has just learned to who plays tournaments. */
8
9
  export const levels = ["learning", "casual", "regular", "tournament"];
9
- /** Where a rating comes from. */
10
- export const ratingSites = ["lichess", "chess.com", "fide"];
11
- /** The lowest and highest rating a profile takes. */
12
- export const ratingLimits = { lowest: 100, highest: 3500 };
13
10
  /** The longest name a profile keeps. */
14
11
  export const nameLimit = 40;
15
12
  /** The avatars Zoe drew, for a player without an image of their own (D-45). */
16
13
  export const drawnAvatars = ["short-hair", "bun", "glasses", "beanie", "beard", "curls", "moustache", "silver-bob", "cat", "owl"];
14
+ /** The FIDE ratings Chesspal takes: whole numbers from FIDE's lowest, 1400 since 2024, to 3000. */
15
+ export const fideLowest = 1400;
16
+ export const fideHighest = 3000;
17
17
  /** The profiles in the data folder, by their folders' names. */
18
18
  export function listProfiles(root) {
19
19
  return profileIds(root).map((id) => readProfile(root, id));
@@ -47,9 +47,9 @@ export function profileFolder(root, id) {
47
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
- const { level, rating, avatar } = fields;
50
+ const { level, avatar, fide } = fields;
51
51
  const named = renamed(root, id, fields.name);
52
- writeProfile(root, named, { name: fields.name, level, rating, avatar, new: false });
52
+ writeProfile(root, named, { name: fields.name, level, avatar, fide, new: false });
53
53
  return readProfile(root, named);
54
54
  }
55
55
  let made = freeId(root, slugOf(fields.name));
@@ -172,22 +172,38 @@ function writeActive(root, id) {
172
172
  }
173
173
  function readProfile(root, id) {
174
174
  const { fields } = readFrontMatter(profileText(root, id));
175
- const value = Number(fields.rating);
176
- const site = oneOf(ratingSites, fields.site);
177
175
  const level = oneOf(levels, fields.level);
178
176
  const avatar = oneOf(drawnAvatars, fields.avatar);
179
- const rated = Number.isInteger(value) && value >= ratingLimits.lowest && value <= ratingLimits.highest && site !== undefined;
177
+ const fide = fideIn(fields.fide);
178
+ const { accounts } = accountsIn(fields);
179
+ const sites = savedReadings(profileFolder(root, id));
180
+ const rating = sites === undefined ? undefined : surest([ofAccount(sites.lichess, accounts?.lichess), ofAccount(sites.chesscom, accounts?.chesscom)]);
180
181
  // A name written by hand is cut as the page and the CLI cut it: the AI reads it.
181
182
  return {
182
183
  id,
183
184
  name: fields.name?.trim().slice(0, nameLimit).trim() || id,
184
185
  ...(level === undefined ? {} : { level }),
185
- ...(rated ? { rating: { value, site } } : {}),
186
186
  ...(avatar === undefined ? {} : { avatar }),
187
+ ...(fide === undefined ? {} : { fide }),
187
188
  ...(fields.new === "true" ? { new: true } : {}),
188
- ...accountsIn(fields),
189
+ ...(accounts === undefined ? {} : { accounts }),
190
+ ...(sites === undefined ? {} : { sites }),
191
+ ...(rating === undefined ? {} : { rating }),
189
192
  };
190
193
  }
194
+ /** A site's rating, when it was read for the name the profile has there now. */
195
+ function ofAccount(reading, account) {
196
+ return account !== undefined && reading?.account.toLowerCase() === account.toLowerCase() ? reading.rating : undefined;
197
+ }
198
+ /** Of the ratings, the one its site is surest of: the lowest RD (D-106). */
199
+ function surest(ratings) {
200
+ return ratings.reduce((best, one) => (one !== undefined && (best === undefined || one.deviation < best.deviation) ? one : best), undefined);
201
+ }
202
+ /** The FIDE rating of profile.md's fide line: none when it is not a whole number in FIDE's range, as one written by hand may not be. */
203
+ function fideIn(text) {
204
+ const fide = Number(text?.trim());
205
+ return Number.isInteger(fide) && fide >= fideLowest && fide <= fideHighest ? fide : undefined;
206
+ }
191
207
  /** The player's names on the sites, from profile.md's lichess and chesscom lines; none without either. */
192
208
  function accountsIn(fields) {
193
209
  const named = (site) => fields[site]?.trim().slice(0, nameLimit).trim() || undefined;
@@ -208,13 +224,12 @@ function writeProfile(root, id, changes) {
208
224
  if ("level" in changes) {
209
225
  written.level = changes.level;
210
226
  }
211
- if ("rating" in changes) {
212
- written.rating = changes.rating?.value;
213
- written.site = changes.rating?.site;
214
- }
215
227
  if ("avatar" in changes) {
216
228
  written.avatar = changes.avatar;
217
229
  }
230
+ if ("fide" in changes) {
231
+ written.fide = changes.fide;
232
+ }
218
233
  if ("accounts" in changes) {
219
234
  written.lichess = changes.accounts?.lichess;
220
235
  written.chesscom = changes.accounts?.chesscom;
@@ -0,0 +1,35 @@
1
+ /** The sites a rating is read from, by the player's names there (D-105). */
2
+ export declare const ratingSites: readonly ["lichess", "chess.com"];
3
+ export type RatingSite = (typeof ratingSites)[number];
4
+ /** The speeds whose rating counts, in the order they count: the games with the AI are like a slow rapid (D-106). */
5
+ export declare const speeds: readonly ["rapid", "classical", "blitz"];
6
+ export type Speed = (typeof speeds)[number];
7
+ /** A rating a site gives: the number as it is, never converted, its speed, how sure the site is of it (its rating deviation, RD), and the day of the last game at that speed. */
8
+ export interface Rating {
9
+ readonly value: number;
10
+ readonly site: RatingSite;
11
+ readonly speed: Speed;
12
+ readonly deviation: number;
13
+ /** YYYY-MM-DD. */
14
+ readonly played?: string;
15
+ }
16
+ /** What a site said of the player's name there, the last time Chesspal asked. */
17
+ export interface SiteReading {
18
+ readonly account: string;
19
+ /** False when the site has no player of that name; unknown while it has never answered. */
20
+ readonly found?: boolean;
21
+ /** The rating that counts there: none when the player has no established rating at a speed that counts. */
22
+ readonly rating?: Rating;
23
+ /** The last try did not reach the site: what it said before stays. */
24
+ readonly unreached?: true;
25
+ }
26
+ /** What the sites said, and when Chesspal last asked them. */
27
+ export interface SiteReadings {
28
+ readonly read: string;
29
+ readonly lichess?: SiteReading;
30
+ readonly chesscom?: SiteReading;
31
+ }
32
+ /** The readings kept in a profile's folder; undefined when it has none, or they cannot be read. */
33
+ export declare function savedReadings(folder: string): SiteReadings | undefined;
34
+ /** Keeps the readings in a profile's folder, over the ones before; none removes them. */
35
+ export declare function saveReadings(folder: string, readings: SiteReadings | undefined): void;
@@ -0,0 +1,61 @@
1
+ // What Lichess and Chess.com said of the player's names there (D-105, D-106): ratings.json in the profile's folder, written by Chesspal each time it reads
2
+ // them, never by hand; the rating that counts is chosen from it as the profile is read.
3
+ import { readFileSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { writeAtomically } from "../games/index.js";
6
+ /** The sites a rating is read from, by the player's names there (D-105). */
7
+ export const ratingSites = ["lichess", "chess.com"];
8
+ /** The speeds whose rating counts, in the order they count: the games with the AI are like a slow rapid (D-106). */
9
+ export const speeds = ["rapid", "classical", "blitz"];
10
+ /** The readings kept in a profile's folder; undefined when it has none, or they cannot be read. */
11
+ export function savedReadings(folder) {
12
+ try {
13
+ return readingsOf(JSON.parse(readFileSync(join(folder, fileName), "utf8")));
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ /** Keeps the readings in a profile's folder, over the ones before; none removes them. */
20
+ export function saveReadings(folder, readings) {
21
+ if (readings === undefined) {
22
+ rmSync(join(folder, fileName), { force: true });
23
+ }
24
+ else {
25
+ writeAtomically(join(folder, fileName), `${JSON.stringify(readings, null, 2)}\n`);
26
+ }
27
+ }
28
+ const fileName = "ratings.json";
29
+ // Readings as the file has them, each part checked: a file edited by hand never gives the AI a rating that is not one.
30
+ function readingsOf(value) {
31
+ const file = value;
32
+ if (typeof file?.read !== "string" || Number.isNaN(Date.parse(file.read))) {
33
+ return undefined;
34
+ }
35
+ const lichess = readingOf(file.lichess, "lichess");
36
+ const chesscom = readingOf(file.chesscom, "chess.com");
37
+ return { read: file.read, ...(lichess === undefined ? {} : { lichess }), ...(chesscom === undefined ? {} : { chesscom }) };
38
+ }
39
+ function readingOf(value, site) {
40
+ const reading = value;
41
+ if (typeof reading?.account !== "string" || reading.account === "") {
42
+ return undefined;
43
+ }
44
+ const rating = ratingOf(reading.rating, site);
45
+ return {
46
+ account: reading.account,
47
+ ...(typeof reading.found === "boolean" ? { found: reading.found } : {}),
48
+ ...(rating === undefined ? {} : { rating }),
49
+ ...(reading.unreached === true ? { unreached: true } : {}),
50
+ };
51
+ }
52
+ function ratingOf(value, site) {
53
+ const rating = value;
54
+ const speed = speeds.find((one) => one === rating?.speed);
55
+ const plausible = (number) => typeof number === "number" && Number.isInteger(number) && number > 0 && number < 4000;
56
+ if (rating?.site !== site || speed === undefined || !plausible(rating.value) || !plausible(rating.deviation)) {
57
+ return undefined;
58
+ }
59
+ const played = typeof rating.played === "string" && /^\d{4}-\d{2}-\d{2}$/.test(rating.played) ? rating.played : undefined;
60
+ return { value: rating.value, site, speed, deviation: rating.deviation, ...(played === undefined ? {} : { played }) };
61
+ }
@@ -29,16 +29,32 @@ export declare class Bell {
29
29
  ring(): void;
30
30
  /** 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. */
31
31
  listen(request: IncomingMessage, response: ServerResponse): void;
32
+ /** The chat left the table (D-104): its process ended, and nobody is in a turn or waits for the bell; a ring no hook heard is no longer news for anyone. */
33
+ chatLeft(): void;
32
34
  private tellIf;
33
35
  }
34
- /** Whose page answers at a port, for the hook to find its own: the chat's session, the player's folder, and whether a browser has the page open. */
35
- export declare function bellProbe(folder: string, open?: boolean): BellProbe;
36
+ /**
37
+ * Whose page answers at a port, for the hook to find its own: the chat's session, the player's folder, whether a browser has the page open, Chesspal's
38
+ * version, and whether the table serves it (D-104), with the session and the folder of the chat attached to it, never its own process's.
39
+ */
40
+ export declare function bellProbe(folder: string, open?: boolean, table?: TableChat): BellProbe;
41
+ /** The chat attached to the table, as its probe says it: its session, null for none or for Codex, and its folder; neither while no chat is attached. */
42
+ export interface TableChat {
43
+ session: string | null;
44
+ chat?: string;
45
+ }
36
46
  /** What a page says of itself at `/api/bell?probe`. */
37
47
  export interface BellProbe {
38
48
  session: string | null;
39
49
  folder: string;
40
50
  open: boolean;
51
+ version?: string;
52
+ table?: true;
53
+ /** The folder of the chat attached to the table. */
54
+ chat?: string;
41
55
  }
56
+ /** What the page at a port of this computer says of itself; undefined when none answers there. */
57
+ export declare function pageAt(port: number): Promise<BellProbe | undefined>;
42
58
  /** 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. */
43
59
  export declare function pagePorts(folder: string): number[];
44
60
  /** What Codex's Stop hook answers to wake the chat: its turn goes on with the doorbell's line. */
@@ -62,7 +78,12 @@ export type BellHeard = {
62
78
  rang: false;
63
79
  why: string;
64
80
  };
65
- /** Waits at the page of the hook's chat, as waitForBell does, and says what it heard. */
81
+ /**
82
+ * Waits at the page of the hook's chat, as waitForBell does, and says what it heard. The chat may be attached to the table (D-104), beside another chat
83
+ * of its folder that serves its own page: the table is one more page to choose from, unless the ports to try are given (Priya).
84
+ */
66
85
  export declare function bellHeard(input: string, ports?: number[]): Promise<BellHeard>;
67
86
  /** 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. */
68
87
  export declare function noteSilence(folder: string, why: string, now?: Date): void;
88
+ /** Whether two paths name the same folder: without regard to case on Windows. */
89
+ export declare function samePath(one: string, other: string): boolean;