chesspal 0.0.1 → 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 (273) hide show
  1. package/README.md +99 -2
  2. package/dist/cli/chat-hook.d.ts +2 -0
  3. package/dist/cli/chat-hook.js +33 -0
  4. package/dist/cli/cli.d.ts +2 -0
  5. package/dist/cli/cli.js +28 -0
  6. package/dist/cli/command-line.d.ts +7 -0
  7. package/dist/cli/command-line.js +177 -0
  8. package/dist/commands/agents.d.ts +26 -0
  9. package/dist/commands/agents.js +72 -0
  10. package/dist/commands/ai-move.d.ts +28 -0
  11. package/dist/commands/ai-move.js +85 -0
  12. package/dist/commands/ai-say.d.ts +22 -0
  13. package/dist/commands/ai-say.js +58 -0
  14. package/dist/commands/analyse-game.d.ts +65 -0
  15. package/dist/commands/analyse-game.js +265 -0
  16. package/dist/commands/annotate-game.d.ts +39 -0
  17. package/dist/commands/annotate-game.js +131 -0
  18. package/dist/commands/answer-lines.d.ts +28 -0
  19. package/dist/commands/answer-lines.js +144 -0
  20. package/dist/commands/clock.d.ts +15 -0
  21. package/dist/commands/clock.js +45 -0
  22. package/dist/commands/coach.d.ts +45 -0
  23. package/dist/commands/coach.js +344 -0
  24. package/dist/commands/context.d.ts +67 -0
  25. package/dist/commands/context.js +37 -0
  26. package/dist/commands/delete-game.d.ts +7 -0
  27. package/dist/commands/delete-game.js +30 -0
  28. package/dist/commands/delete-review.d.ts +8 -0
  29. package/dist/commands/delete-review.js +34 -0
  30. package/dist/commands/draw-offer.d.ts +10 -0
  31. package/dist/commands/draw-offer.js +43 -0
  32. package/dist/commands/drawings.d.ts +13 -0
  33. package/dist/commands/drawings.js +25 -0
  34. package/dist/commands/end-game.d.ts +23 -0
  35. package/dist/commands/end-game.js +67 -0
  36. package/dist/commands/export-game.d.ts +14 -0
  37. package/dist/commands/export-game.js +27 -0
  38. package/dist/commands/game-summary.d.ts +78 -0
  39. package/dist/commands/game-summary.js +102 -0
  40. package/dist/commands/index.d.ts +18 -0
  41. package/dist/commands/index.js +64 -0
  42. package/dist/commands/init.d.ts +15 -0
  43. package/dist/commands/init.js +73 -0
  44. package/dist/commands/lead-input.d.ts +8 -0
  45. package/dist/commands/lead-input.js +21 -0
  46. package/dist/commands/levels.d.ts +9 -0
  47. package/dist/commands/levels.js +25 -0
  48. package/dist/commands/look.d.ts +31 -0
  49. package/dist/commands/look.js +64 -0
  50. package/dist/commands/new-game.d.ts +68 -0
  51. package/dist/commands/new-game.js +218 -0
  52. package/dist/commands/opening-book.d.ts +13 -0
  53. package/dist/commands/opening-book.js +84 -0
  54. package/dist/commands/page-link.d.ts +11 -0
  55. package/dist/commands/page-link.js +28 -0
  56. package/dist/commands/past-games.d.ts +11 -0
  57. package/dist/commands/past-games.js +47 -0
  58. package/dist/commands/play-move.d.ts +37 -0
  59. package/dist/commands/play-move.js +94 -0
  60. package/dist/commands/player-move.d.ts +13 -0
  61. package/dist/commands/player-move.js +51 -0
  62. package/dist/commands/player-say.d.ts +5 -0
  63. package/dist/commands/player-say.js +16 -0
  64. package/dist/commands/ponder.d.ts +52 -0
  65. package/dist/commands/ponder.js +179 -0
  66. package/dist/commands/position.d.ts +14 -0
  67. package/dist/commands/position.js +130 -0
  68. package/dist/commands/profiles.d.ts +69 -0
  69. package/dist/commands/profiles.js +163 -0
  70. package/dist/commands/registry.d.ts +59 -0
  71. package/dist/commands/registry.js +62 -0
  72. package/dist/commands/say.d.ts +16 -0
  73. package/dist/commands/say.js +84 -0
  74. package/dist/commands/speech.d.ts +8 -0
  75. package/dist/commands/speech.js +20 -0
  76. package/dist/commands/status.d.ts +35 -0
  77. package/dist/commands/status.js +128 -0
  78. package/dist/commands/study-games.d.ts +5 -0
  79. package/dist/commands/study-games.js +40 -0
  80. package/dist/commands/study.d.ts +38 -0
  81. package/dist/commands/study.js +106 -0
  82. package/dist/commands/view.d.ts +14 -0
  83. package/dist/commands/view.js +125 -0
  84. package/dist/core/board-text.d.ts +7 -0
  85. package/dist/core/board-text.js +18 -0
  86. package/dist/core/book.json +1 -0
  87. package/dist/core/clock.d.ts +45 -0
  88. package/dist/core/clock.js +109 -0
  89. package/dist/core/facts.d.ts +55 -0
  90. package/dist/core/facts.js +175 -0
  91. package/dist/core/game.d.ts +95 -0
  92. package/dist/core/game.js +352 -0
  93. package/dist/core/illegal.d.ts +19 -0
  94. package/dist/core/illegal.js +141 -0
  95. package/dist/core/index.d.ts +20 -0
  96. package/dist/core/index.js +12 -0
  97. package/dist/core/lead.d.ts +15 -0
  98. package/dist/core/lead.js +40 -0
  99. package/dist/core/levels.d.ts +20 -0
  100. package/dist/core/levels.js +33 -0
  101. package/dist/core/material.d.ts +9 -0
  102. package/dist/core/material.js +12 -0
  103. package/dist/core/moods.d.ts +21 -0
  104. package/dist/core/moods.js +45 -0
  105. package/dist/core/openings.d.ts +35 -0
  106. package/dist/core/openings.js +98 -0
  107. package/dist/core/openings.json +1 -0
  108. package/dist/core/pgn.d.ts +10 -0
  109. package/dist/core/pgn.js +213 -0
  110. package/dist/core/profile.d.ts +117 -0
  111. package/dist/core/profile.js +232 -0
  112. package/dist/core/record.d.ts +21 -0
  113. package/dist/core/record.js +25 -0
  114. package/dist/core/review.d.ts +80 -0
  115. package/dist/core/review.js +146 -0
  116. package/dist/games/ai-model.d.ts +5 -0
  117. package/dist/games/ai-model.js +15 -0
  118. package/dist/games/analysis.d.ts +12 -0
  119. package/dist/games/analysis.js +26 -0
  120. package/dist/games/archive.d.ts +63 -0
  121. package/dist/games/archive.js +242 -0
  122. package/dist/games/chat-start.d.ts +16 -0
  123. package/dist/games/chat-start.js +26 -0
  124. package/dist/games/coach.d.ts +57 -0
  125. package/dist/games/coach.js +144 -0
  126. package/dist/games/current-game.d.ts +56 -0
  127. package/dist/games/current-game.js +134 -0
  128. package/dist/games/files.d.ts +9 -0
  129. package/dist/games/files.js +53 -0
  130. package/dist/games/index.d.ts +11 -0
  131. package/dist/games/index.js +12 -0
  132. package/dist/games/plans.d.ts +9 -0
  133. package/dist/games/plans.js +23 -0
  134. package/dist/games/table-talk.d.ts +12 -0
  135. package/dist/games/table-talk.js +25 -0
  136. package/dist/games/talk.d.ts +36 -0
  137. package/dist/games/talk.js +131 -0
  138. package/dist/games/thoughts.d.ts +15 -0
  139. package/dist/games/thoughts.js +60 -0
  140. package/dist/home/data-folder.d.ts +12 -0
  141. package/dist/home/data-folder.js +55 -0
  142. package/dist/home/index.d.ts +2 -0
  143. package/dist/home/index.js +3 -0
  144. package/dist/home/open-folder.d.ts +2 -0
  145. package/dist/home/open-folder.js +14 -0
  146. package/dist/host-files/agents.d.ts +20 -0
  147. package/dist/host-files/agents.js +53 -0
  148. package/dist/host-files/host-files.d.ts +15 -0
  149. package/dist/host-files/host-files.js +182 -0
  150. package/dist/host-files/index.d.ts +2 -0
  151. package/dist/host-files/index.js +3 -0
  152. package/dist/index.d.ts +2 -0
  153. package/dist/index.js +3 -0
  154. package/dist/page/app/anchor.js +88 -0
  155. package/dist/page/app/app.js +458 -0
  156. package/dist/page/app/keys.js +94 -0
  157. package/dist/page/app/phase.js +30 -0
  158. package/dist/page/app/served.js +73 -0
  159. package/dist/page/avatars/avatars.js +19 -0
  160. package/dist/page/avatars/beanie.webp +0 -0
  161. package/dist/page/avatars/beard.webp +0 -0
  162. package/dist/page/avatars/bun.webp +0 -0
  163. package/dist/page/avatars/cat.webp +0 -0
  164. package/dist/page/avatars/curls.webp +0 -0
  165. package/dist/page/avatars/glasses.webp +0 -0
  166. package/dist/page/avatars/moustache.webp +0 -0
  167. package/dist/page/avatars/owl.webp +0 -0
  168. package/dist/page/avatars/short-hair.webp +0 -0
  169. package/dist/page/avatars/silver-bob.webp +0 -0
  170. package/dist/page/avatars/thought.js +10 -0
  171. package/dist/page/avatars/torretta.js +38 -0
  172. package/dist/page/base.css +160 -0
  173. package/dist/page/board/board.css +959 -0
  174. package/dist/page/board/board.js +125 -0
  175. package/dist/page/board/explain.js +58 -0
  176. package/dist/page/board/marks.js +160 -0
  177. package/dist/page/board/motion.js +71 -0
  178. package/dist/page/board/pointer.js +239 -0
  179. package/dist/page/board/squares.js +26 -0
  180. package/dist/page/dialogs/analyses.js +195 -0
  181. package/dist/page/dialogs/dialog.js +26 -0
  182. package/dist/page/dialogs/dialogs.css +1228 -0
  183. package/dist/page/dialogs/new-game.js +42 -0
  184. package/dist/page/dialogs/profiles.js +152 -0
  185. package/dist/page/dialogs/search.js +31 -0
  186. package/dist/page/dialogs/settings.js +42 -0
  187. package/dist/page/dialogs/studied.js +39 -0
  188. package/dist/page/dialogs/study.js +51 -0
  189. package/dist/page/game/badges.js +50 -0
  190. package/dist/page/game/browse.js +17 -0
  191. package/dist/page/game/card-fold.js +40 -0
  192. package/dist/page/game/column.js +51 -0
  193. package/dist/page/game/ending.js +73 -0
  194. package/dist/page/game/game.css +577 -0
  195. package/dist/page/game/move-list.js +72 -0
  196. package/dist/page/game/opening-book.js +14 -0
  197. package/dist/page/game/plies.js +82 -0
  198. package/dist/page/game/result-tile.js +14 -0
  199. package/dist/page/helps/helps.css +689 -0
  200. package/dist/page/helps/helps.js +220 -0
  201. package/dist/page/helps/view.js +127 -0
  202. package/dist/page/layout/fold-tab.js +11 -0
  203. package/dist/page/layout/folds.js +43 -0
  204. package/dist/page/layout/header.js +28 -0
  205. package/dist/page/layout/layout.css +834 -0
  206. package/dist/page/lead/lead.js +82 -0
  207. package/dist/page/lead/view.js +38 -0
  208. package/dist/page/main.js +8 -0
  209. package/dist/page/pieces/CREDITS.md +19 -0
  210. package/dist/page/pieces/capablanca-small.svg +20 -0
  211. package/dist/page/pieces/capablanca.svg +22 -0
  212. package/dist/page/pieces/nitida-small.svg +20 -0
  213. package/dist/page/pieces/nitida.svg +21 -0
  214. package/dist/page/pieces/pieces.js +28 -0
  215. package/dist/page/pieces/torretta-small.svg +20 -0
  216. package/dist/page/pieces/torretta.svg +21 -0
  217. package/dist/page/players/card.js +56 -0
  218. package/dist/page/players/clock-time.js +12 -0
  219. package/dist/page/players/model-name.js +20 -0
  220. package/dist/page/profile/profile.js +99 -0
  221. package/dist/page/profile/settings.js +77 -0
  222. package/dist/page/sounds/capture.ogg +0 -0
  223. package/dist/page/sounds/check.ogg +0 -0
  224. package/dist/page/sounds/draw.ogg +0 -0
  225. package/dist/page/sounds/flag.ogg +0 -0
  226. package/dist/page/sounds/mate.ogg +0 -0
  227. package/dist/page/sounds/move.ogg +0 -0
  228. package/dist/page/sounds/offer.ogg +0 -0
  229. package/dist/page/sounds/resign.ogg +0 -0
  230. package/dist/page/sounds/sounds.js +105 -0
  231. package/dist/page/style.css +9 -0
  232. package/dist/page/talk/talk.css +958 -0
  233. package/dist/page/talk/talk.js +165 -0
  234. package/dist/page/talk/voice.js +38 -0
  235. package/dist/page/text.js +711 -0
  236. package/dist/page/theme.css +292 -0
  237. package/dist/profiles/avatar.d.ts +23 -0
  238. package/dist/profiles/avatar.js +84 -0
  239. package/dist/profiles/front-matter.d.ts +9 -0
  240. package/dist/profiles/front-matter.js +33 -0
  241. package/dist/profiles/index.d.ts +3 -0
  242. package/dist/profiles/index.js +5 -0
  243. package/dist/profiles/profiles.d.ts +69 -0
  244. package/dist/profiles/profiles.js +254 -0
  245. package/dist/profiles/settings.d.ts +12 -0
  246. package/dist/profiles/settings.js +46 -0
  247. package/dist/server/bell.d.ts +68 -0
  248. package/dist/server/bell.js +225 -0
  249. package/dist/server/caller.d.ts +21 -0
  250. package/dist/server/caller.js +132 -0
  251. package/dist/server/mcp.d.ts +13 -0
  252. package/dist/server/mcp.js +207 -0
  253. package/dist/server/page/analyses.d.ts +23 -0
  254. package/dist/server/page/analyses.js +65 -0
  255. package/dist/server/page/files.d.ts +5 -0
  256. package/dist/server/page/files.js +92 -0
  257. package/dist/server/page/game.d.ts +6 -0
  258. package/dist/server/page/game.js +381 -0
  259. package/dist/server/page/http.d.ts +29 -0
  260. package/dist/server/page/http.js +68 -0
  261. package/dist/server/page/page.d.ts +41 -0
  262. package/dist/server/page/page.js +229 -0
  263. package/dist/server/page/profiles.d.ts +19 -0
  264. package/dist/server/page/profiles.js +215 -0
  265. package/dist/version.d.ts +2 -0
  266. package/dist/version.js +3 -0
  267. package/instructions/analysis.md +4 -0
  268. package/instructions/analyst-agent.md +8 -0
  269. package/instructions/analyst.md +4 -0
  270. package/instructions/coach.md +11 -0
  271. package/instructions/opponent.md +15 -0
  272. package/instructions/ponder.md +9 -0
  273. package/package.json +66 -5
@@ -0,0 +1,64 @@
1
+ // The commands: the only way the AI, the command line and the page change a game (D-5, D-27), each defined once in the file named as its tool.
2
+ import { aiMove } from "./ai-move.js";
3
+ import { aiSay } from "./ai-say.js";
4
+ import { analyseGame } from "./analyse-game.js";
5
+ import { annotateGame } from "./annotate-game.js";
6
+ import { coach } from "./coach.js";
7
+ import { deleteGame } from "./delete-game.js";
8
+ import { deleteReview } from "./delete-review.js";
9
+ import { endGame } from "./end-game.js";
10
+ import { exportGame } from "./export-game.js";
11
+ import { init } from "./init.js";
12
+ import { look } from "./look.js";
13
+ import { newGame } from "./new-game.js";
14
+ import { pastGames } from "./past-games.js";
15
+ import { playerMove } from "./player-move.js";
16
+ import { playerSay } from "./player-say.js";
17
+ import { ponder } from "./ponder.js";
18
+ import { position } from "./position.js";
19
+ import { newProfile, profiles, setAccounts, setAvatar, useProfile } from "./profiles.js";
20
+ import { status } from "./status.js";
21
+ import { studyGames } from "./study-games.js";
22
+ // What the server and the command line use besides the list: the context, the reading of an input, and the operations the page shares with the tools.
23
+ export { aiMove } from "./ai-move.js";
24
+ export { analyseGame } from "./analyse-game.js";
25
+ export { gameAt } from "./clock.js";
26
+ export { defaultContext } from "./context.js";
27
+ export { deleteGame } from "./delete-game.js";
28
+ export { deleteReview } from "./delete-review.js";
29
+ export { answerDraw, DrawRefused, offerDraw } from "./draw-offer.js";
30
+ export { endGame } from "./end-game.js";
31
+ export { ChatNotClear, newGame, startNewGame } from "./new-game.js";
32
+ export { bookOpening, bookPlies } from "./opening-book.js";
33
+ export { playMove, PositionChanged, positionIn, samePosition } from "./play-move.js";
34
+ export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.js";
35
+ export { baseType, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.js";
36
+ export { say } from "./say.js";
37
+ export { gameToStudy, namedGame } from "./study.js";
38
+ /** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
39
+ export const commands = [
40
+ init,
41
+ status,
42
+ newGame,
43
+ playerMove,
44
+ playerSay,
45
+ aiMove,
46
+ aiSay,
47
+ ponder,
48
+ coach,
49
+ look,
50
+ position,
51
+ endGame,
52
+ exportGame,
53
+ pastGames,
54
+ analyseGame,
55
+ annotateGame,
56
+ studyGames,
57
+ deleteReview,
58
+ deleteGame,
59
+ profiles,
60
+ newProfile,
61
+ useProfile,
62
+ setAvatar,
63
+ setAccounts,
64
+ ];
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ /** The player's folder set up for the AI hosts: the folder, and the files written there. */
3
+ interface FolderSetUp {
4
+ folder: string;
5
+ written: string[];
6
+ /** The folder as the player goes into it from where init ran, when it is not that one: chess. */
7
+ enter?: string;
8
+ /** Codex's hooks changed in a folder that had them: Codex runs them only once the player trusts them again. */
9
+ hooksChanged?: true;
10
+ }
11
+ /** Makes a folder the player's chess table: the files through which Claude Code and Codex find Chesspal and the opponent's instructions (D-31). */
12
+ export declare const init: import("./registry.ts").Command<z.ZodObject<{
13
+ folder: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>, FolderSetUp>;
15
+ export {};
@@ -0,0 +1,73 @@
1
+ import { existsSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+ // Colours only on a terminal that shows them: none in a file, a pipe or with NO_COLOR.
4
+ import { styleText } from "node:util";
5
+ import { z } from "zod";
6
+ import { codexHooksPath, setUpHosts } from "../host-files/index.js";
7
+ import { defineCommand } from "./registry.js";
8
+ /** Makes a folder the player's chess table: the files through which Claude Code and Codex find Chesspal and the opponent's instructions (D-31). */
9
+ export const init = defineCommand({
10
+ name: "init",
11
+ description: "Sets up a folder to play chess with an AI: the .mcp.json, AGENTS.md, CLAUDE.md and the settings through which Claude Code and Codex find "
12
+ + "Chesspal. Run it again to update Chesspal's lines; what you wrote there stays. Example: chesspal init chess.",
13
+ input: z.object({ folder: z.string().optional().describe("the folder to set up; the current folder if left out") }),
14
+ cliOnly: true,
15
+ idempotent: true,
16
+ positionals: ["folder"],
17
+ run: ({ folder }, context) => {
18
+ const root = resolve(context.folder, folder ?? ".");
19
+ const hooks = codexHooksPath(root);
20
+ const hadHooks = existsSync(hooks);
21
+ const written = setUpHosts(root, context.platform);
22
+ const enter = relative(context.folder, root);
23
+ return {
24
+ folder: root,
25
+ written: written.map((path) => relative(root, path)),
26
+ ...(enter === "" ? {} : { enter }),
27
+ ...(hadHooks && written.includes(hooks) ? { hooksChanged: true } : {}),
28
+ };
29
+ },
30
+ format: ({ folder, written, enter, hooksChanged }, context) => {
31
+ const words = wordsFor(context?.language);
32
+ const steps = [words.open(enter === undefined ? undefined : quoted(enter)), ...words.steps];
33
+ return [
34
+ styleText(["bold", "green"], `♜ ${written.length === 0 ? words.ready(folder) : words.set(folder)}`),
35
+ "",
36
+ ` ${words.toPlay}`,
37
+ ...steps.map((step, index) => ` ${styleText("cyan", `${index + 1}.`)} ${step.join("\n ")}`),
38
+ ...(hooksChanged === true ? ["", ` ${styleText("yellow", words.hooksChanged)}`] : []),
39
+ ...(written.length === 0 ? [] : ["", styleText("gray", ` ${words.updated(written.length)}`)]),
40
+ ].join("\n");
41
+ },
42
+ });
43
+ const english = {
44
+ set: (folder) => `Chesspal is ready in ${folder}`,
45
+ ready: (folder) => `Chesspal is ready in ${folder}: nothing to change`,
46
+ toPlay: "To play:",
47
+ open: (enter) => enter === undefined ? ["Open Claude Code or Codex in this folder."] : [`Go into the folder: cd ${enter}`, "then open Claude Code (claude) or Codex (codex)."],
48
+ steps: [
49
+ ["The first time, it asks whether to trust the folder: say yes.", "Codex also asks about Chesspal's two hooks: choose Trust all.", "They are small helpers that let it notice your moves on the board."],
50
+ ["Say hello: the AI gives you the link to the board."],
51
+ ],
52
+ hooksChanged: "Chesspal's hooks for Codex have changed: when Codex asks about them, choose Trust all again.",
53
+ updated: (count) => `Updated ${count} ${count === 1 ? "file" : "files"}.`,
54
+ };
55
+ const italian = {
56
+ set: (folder) => `Chesspal è pronto in ${folder}`,
57
+ ready: (folder) => `Chesspal è già pronto in ${folder}: niente da cambiare`,
58
+ toPlay: "Per giocare:",
59
+ open: (enter) => enter === undefined ? ["Apri Claude Code o Codex in questa cartella."] : [`Entra nella cartella: cd ${enter}`, "poi apri Claude Code (claude) o Codex (codex)."],
60
+ steps: [
61
+ ["La prima volta ti chiede se fidarti della cartella: rispondi sì.", "Codex chiede anche dei due «hook» di Chesspal: scegli Trust all.", "Sono piccoli aiuti che gli fanno vedere le tue mosse sulla scacchiera."],
62
+ ["Saluta: l'AI ti dà il link della scacchiera."],
63
+ ],
64
+ hooksChanged: "Gli «hook» di Chesspal per Codex sono cambiati: quando Codex te lo chiede, scegli di nuovo Trust all.",
65
+ updated: (count) => count === 1 ? "Aggiornato 1 file." : `Aggiornati ${count} file.`,
66
+ };
67
+ function wordsFor(language) {
68
+ return language?.slice(0, 2).toLowerCase() === "it" ? italian : english;
69
+ }
70
+ /** A path as a shell reads it as one word: in quotes when it has spaces. */
71
+ function quoted(path) {
72
+ return /\s/.test(path) ? `"${path}"` : path;
73
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ import { type Lead } from "../core/index.ts";
3
+ /** A lead as its input reads it: pawns from White's side as a number, +1.5 or -3, or a mate it sees, "#3" White's or "#-3" Black's. */
4
+ export declare const leadInput: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
5
+ /** Who is ahead in words, the side and by how much, so that the AI reads its own sign back (Anatolij). */
6
+ export declare function aheadWords(lead: Lead): string;
7
+ /** The lead an input gave, checked by leadInput. */
8
+ export declare function leadFrom(value: number | string): Lead;
@@ -0,0 +1,21 @@
1
+ // Who is ahead as the AI gives it (D-90): the analyst after each move of a game over, the coach in the position the player is to move in.
2
+ import { z } from "zod";
3
+ import { readLead } from "../core/index.js";
4
+ /** A lead as its input reads it: pawns from White's side as a number, +1.5 or -3, or a mate it sees, "#3" White's or "#-3" Black's. */
5
+ export const leadInput = z.union([z.number(), z.string()]).refine((value) => readLead(String(value)) !== undefined, 'who is ahead is a number of pawns from White\'s side, 1.5 or -3, or a mate you see in moves of the side that mates, "#3" for White\'s or "#-3" for Black\'s, '
6
+ + "up to 99");
7
+ /** Who is ahead in words, the side and by how much, so that the AI reads its own sign back (Anatolij). */
8
+ export function aheadWords(lead) {
9
+ if ("mate" in lead) {
10
+ return `${lead.mate > 0 ? "White" : "Black"} mates in ${Math.abs(lead.mate)}`;
11
+ }
12
+ return lead.pawns === 0 ? "even" : `${lead.pawns > 0 ? "White" : "Black"} ahead by ${Math.abs(lead.pawns)}`;
13
+ }
14
+ /** The lead an input gave, checked by leadInput. */
15
+ export function leadFrom(value) {
16
+ const lead = readLead(String(value));
17
+ if (lead === undefined) {
18
+ throw new Error(`${value} is not who is ahead: give a number of pawns from White's side, 1.5 or -3, or a mate you see, "#3" or "#-3". Nothing changed.`);
19
+ }
20
+ return lead;
21
+ }
@@ -0,0 +1,9 @@
1
+ import { type AiLevel, type Layer } from "../core/index.ts";
2
+ import { type CurrentGame } from "../games/index.ts";
3
+ import type { Context } from "./context.ts";
4
+ /** The level of a new game when the call names none: the game before's, else the one that suits the player in use, else the level before levels came. */
5
+ export declare function levelToPlay(context: Context, before: CurrentGame | undefined): AiLevel;
6
+ /** The level and what the AI sees at it, in few words: at the club level (board and tactical facts: no position). */
7
+ export declare function levelText(level: AiLevel): string;
8
+ /** Throws when the game's level does not give a layer, saying what the level gives instead; a game over gives every layer, to its analysis (D-81). */
9
+ export declare function refuseUnseen(current: CurrentGame, layer: Layer, tool: string): void;
@@ -0,0 +1,25 @@
1
+ // The level of a game as the AI reads it (D-60, D-61): what it sees, and the refusal of a tool its level does not give.
2
+ import { levelOf, sees, suggestedLevel } from "../core/index.js";
3
+ import { endOf } from "../games/index.js";
4
+ import { activeProfile } from "../profiles/index.js";
5
+ /** The level of a new game when the call names none: the game before's, else the one that suits the player in use, else the level before levels came. */
6
+ export function levelToPlay(context, before) {
7
+ return suggestedLevel(before?.game.record, before === undefined ? activeProfile(context.home()).level : undefined);
8
+ }
9
+ /** The level and what the AI sees at it, in few words: at the club level (board and tactical facts: no position). */
10
+ export function levelText(level) {
11
+ return `at the ${level} level (${seenText[level]})`;
12
+ }
13
+ /** Throws when the game's level does not give a layer, saying what the level gives instead; a game over gives every layer, to its analysis (D-81). */
14
+ export function refuseUnseen(current, layer, tool) {
15
+ const level = levelOf(current.game.record);
16
+ if (!sees(level, layer) && endOf(current.game) === undefined) {
17
+ throw new Error(`${tool} is not yours in this game: you play ${levelText(level)}. Decide with the facts you have.`);
18
+ }
19
+ }
20
+ const seenText = {
21
+ beginner: "board and legal moves: no tactical facts, no position",
22
+ club: "board and tactical facts: no position",
23
+ strong: "board, tactical and positional facts",
24
+ master: "board, tactical and positional facts, opening book",
25
+ };
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { type Facts, type Side } from "../core/index.ts";
3
+ import { type GameSummary } from "./game-summary.ts";
4
+ /** The game with the facts of its position, whoever is to move. */
5
+ type Viewed = GameSummary & {
6
+ facts: Facts;
7
+ };
8
+ /** A position of a game studied (D-68): where it stands, and its facts. */
9
+ interface StudyView {
10
+ where: string;
11
+ turn: Side;
12
+ fen: string;
13
+ facts: Facts;
14
+ }
15
+ /** The whole view (D-26), when the compact one after the player's move is not enough; or the board for the player, where the host does not show it (D-36). */
16
+ export declare const look: import("./registry.ts").Command<z.ZodObject<{
17
+ game: z.ZodOptional<z.ZodString>;
18
+ move: z.ZodOptional<z.ZodString>;
19
+ only: z.ZodOptional<z.ZodEnum<{
20
+ board: "board";
21
+ }>>;
22
+ }, z.core.$strip>, {
23
+ board: string;
24
+ } | {
25
+ game: Viewed;
26
+ words: string[];
27
+ } | {
28
+ study: StudyView;
29
+ words: string[];
30
+ }>;
31
+ export {};
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { boardText, materialOf } from "../core/index.js";
3
+ import { analystAgent } from "../host-files/index.js";
4
+ import { gameFolder } from "../profiles/index.js";
5
+ import { headLines, wordLines } from "./answer-lines.js";
6
+ import { gameAt } from "./clock.js";
7
+ import { summaryOf } from "./game-summary.js";
8
+ import { refuseUnseen } from "./levels.js";
9
+ import { defineCommand } from "./registry.js";
10
+ import { newWords } from "./say.js";
11
+ import { gameToStudy, positionAfter, positionLine, studyInput } from "./study.js";
12
+ import { fullView, materialText } from "./view.js";
13
+ /** The whole view (D-26), when the compact one after the player's move is not enough; or the board for the player, where the host does not show it (D-36). */
14
+ export const look = defineCommand({
15
+ name: "look",
16
+ description: "Shows the whole view of the position, from the club level up and at every level once the game is over: the FEN and, for every piece, what attacks and defends it, then the pins, the checks and the legal "
17
+ + "moves of each piece. The views of player_move, status and new_game give attackers and defenders only for the pieces attacked: call look "
18
+ + 'before you attack or capture a piece that is not attacked yet, or plan an exchange. With only: "board", the board as text for the player, to '
19
+ + "copy into your message where they do not see it. With move, and game for a game of the archive, the same of any position of a game over, to "
20
+ + 'analyse it. Example: look {}, look {move: "12..."}.',
21
+ input: z.object({ only: z.enum(["board"]).optional().describe("board: only the board as text, for the player"), ...studyInput }),
22
+ // Read only for the host: a flag that has fallen is written, as the time already decided.
23
+ readOnly: true,
24
+ idempotent: true,
25
+ sharedWith: [analystAgent],
26
+ run: ({ only, game, move }, context) => {
27
+ if (game !== undefined || move !== undefined) {
28
+ return studyLook(context, only, game, move);
29
+ }
30
+ const current = gameAt(gameFolder(context.home()), context.now());
31
+ if (current === undefined) {
32
+ throw new Error("There is no game to look at: start one with new_game.");
33
+ }
34
+ if (only !== "board") {
35
+ refuseUnseen(current, "tactics", 'look without only: "board"');
36
+ }
37
+ return only === "board" ? { board: boardText(current.game.position.fen) } : { game: { ...summaryOf(current, context.now()), facts: current.game.facts }, words: newWords(context) };
38
+ },
39
+ format: (look) => {
40
+ if ("board" in look) {
41
+ return look.board;
42
+ }
43
+ return [...("game" in look ? [...headLines(look.game), ...fullView(look.game.facts, look.game.turn)] : studyLines(look.study)), ...wordLines(look.words)].join("\n");
44
+ },
45
+ });
46
+ /** A position of a game over, as the view of the game going on gives it. */
47
+ function studyLook(context, only, name, move) {
48
+ const studied = gameToStudy(context, name);
49
+ const at = positionAfter(studied.record, move);
50
+ const { position } = at.game;
51
+ if (only === "board") {
52
+ return { board: boardText(position.fen) };
53
+ }
54
+ return { study: { where: positionLine(at, studied.name), turn: position.turn, fen: position.fen, facts: at.game.facts }, words: newWords(context) };
55
+ }
56
+ /** Where the position stands, the material, the FEN, and the whole view. */
57
+ function studyLines({ where, turn, fen, facts }) {
58
+ return [
59
+ where,
60
+ `Material: ${materialText(materialOf(fen))}`,
61
+ `FEN: ${fen}`,
62
+ ...fullView(facts, turn),
63
+ ];
64
+ }
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import type { Context } from "./context.ts";
3
+ import { type GameSummary } from "./game-summary.ts";
4
+ import { type Speech } from "./speech.ts";
5
+ interface NewGame {
6
+ game: GameSummary;
7
+ /** Where the game before went: one going on, unfinished; one over, when its archive could not be written at its end. */
8
+ archived?: {
9
+ name: string;
10
+ finished: boolean;
11
+ };
12
+ /** The last games played with this player, for a chat that starts clean (D-57). */
13
+ past: string[];
14
+ /** Who plays, from the profile in use (D-23), once the player has completed it (D-71). */
15
+ player?: string;
16
+ /** The player's language when it is not English. */
17
+ speech?: Speech;
18
+ /** The board page's address when no browser has it open: the player needs it first. */
19
+ closedPage?: string;
20
+ }
21
+ /** The player chooses the game in the board page's window instead: open in a browser, or to be opened first at its address. */
22
+ interface Asked {
23
+ window: "opened" | "closed";
24
+ page?: string;
25
+ }
26
+ declare const newGameInput: z.ZodObject<{
27
+ ai_colour: z.ZodOptional<z.ZodEnum<{
28
+ white: "white";
29
+ black: "black";
30
+ random: "random";
31
+ }>>;
32
+ from: z.ZodOptional<z.ZodString>;
33
+ clock: z.ZodOptional<z.ZodString>;
34
+ level: z.ZodOptional<z.ZodEnum<{
35
+ beginner: "beginner";
36
+ club: "club";
37
+ strong: "strong";
38
+ master: "master";
39
+ }>>;
40
+ replace: z.ZodDefault<z.ZodBoolean>;
41
+ }, z.core.$strip>;
42
+ /** A new game against the player, from the standard position or from a FEN or PGN; a game going on is never lost (D-41). */
43
+ export declare const newGame: import("./registry.ts").Command<z.ZodObject<{
44
+ ai_colour: z.ZodOptional<z.ZodEnum<{
45
+ white: "white";
46
+ black: "black";
47
+ random: "random";
48
+ }>>;
49
+ from: z.ZodOptional<z.ZodString>;
50
+ clock: z.ZodOptional<z.ZodString>;
51
+ level: z.ZodOptional<z.ZodEnum<{
52
+ beginner: "beginner";
53
+ club: "club";
54
+ strong: "strong";
55
+ master: "master";
56
+ }>>;
57
+ replace: z.ZodDefault<z.ZodBoolean>;
58
+ }, z.core.$strip>, NewGame | Asked>;
59
+ /**
60
+ * Starts the new game with the player's choices: those the AI asked in the chat, or the page's window gave (D-76). Only the chat's is refused on a game
61
+ * changed since it saw it (D-71): the player who presses New game on the page sees the game there (Federico, round of D-85).
62
+ */
63
+ export declare function startNewGame({ ai_colour: colour, from, clock, level: chosen, replace }: z.output<typeof newGameInput>, context: Context, by: "chat" | "page"): NewGame;
64
+ /** The refusal of a new game in a chat that still holds the game before (D-57): the board page says it in the player's language. */
65
+ export declare class ChatNotClear extends Error {
66
+ constructor();
67
+ }
68
+ export {};
@@ -0,0 +1,218 @@
1
+ import { z } from "zod";
2
+ import { aiLevels, Game, levelTag, pgnDate, readPgn, startClock, timeControlOf, timeControlTag, withoutMoods } from "../core/index.js";
3
+ import { archivedName, archiveGame, cleanSince, endAnalysis, endOf, endTableTalk, lastChatStart, loadGame, saveGame, startOfNewGame, withoutPlans } from "../games/index.js";
4
+ import { gameFolder, gameFolders } from "../profiles/index.js";
5
+ import { coachRuns } from "./agents.js";
6
+ import { nextStep, positionLines, sidesLine } from "./answer-lines.js";
7
+ import { gameAt } from "./clock.js";
8
+ import { summaryOf, withThought } from "./game-summary.js";
9
+ import { levelText, levelToPlay } from "./levels.js";
10
+ import { closedPage, pageGiven, pageLine } from "./page-link.js";
11
+ import { pastGameLines } from "./past-games.js";
12
+ import { refuseUnseenPosition } from "./play-move.js";
13
+ import { playerLine } from "./profiles.js";
14
+ import { defineCommand } from "./registry.js";
15
+ import { speechLine, speechOf } from "./speech.js";
16
+ const newGameInput = z.object({
17
+ ai_colour: z.enum(["white", "black", "random"]).optional().describe("the colour you play: random draws it; left out with the rest, the page's window asks"),
18
+ from: z.string().optional().describe("a FEN or a PGN to go on from, instead of the standard position"),
19
+ clock: z.string().optional().describe("the time control, minutes each and seconds added after each move, such as 10+5 or 3+0; none, or left out, for a game without a clock"),
20
+ level: z.enum(aiLevels).optional().describe("the facts you, not the player, get: beginner the board and the legal moves, club also the tactical facts, strong also the positional ones, master "
21
+ + "also the opening book's moves; left out, the last game's, or for the first game the one that suits the player's profile"),
22
+ replace: z.boolean().default(false).describe("start even though a game is going on: it goes to the archive unfinished"),
23
+ });
24
+ /** A new game against the player, from the standard position or from a FEN or PGN; a game going on is never lost (D-41). */
25
+ export const newGame = defineCommand({
26
+ name: "new_game",
27
+ description: "Starts a new game against the player. Without choices it opens the board page's New game window, where the player chooses the colour, the clock "
28
+ + "and the level; where there is no board page, it tells you to ask them in the chat. With choices it starts the game: only where there is no board page, "
29
+ + 'or from a FEN or PGN the player gives. Example: new_game {}; new_game {ai_colour: "black", from: "<FEN or PGN>", level: "club"}.',
30
+ input: newGameInput,
31
+ alwaysLoad: true,
32
+ showsGame: true,
33
+ positionals: ["ai_colour"],
34
+ run: (input, context) => {
35
+ const { ai_colour: colour, from, clock, level } = input;
36
+ if (colour === undefined && from === undefined && clock === undefined && level === undefined) {
37
+ return askedOnPage(context);
38
+ }
39
+ // The game is played on the board page, and chosen in its window (D-85); a position to go on from is what the window cannot set.
40
+ if (context.page !== undefined && from === undefined) {
41
+ throw new Error("The player chooses a new game in the board page's New game window: call new_game without choices, which opens it, or gives you its address "
42
+ + "when nobody has the page open. Nothing changed.");
43
+ }
44
+ return startNewGame(input, context, "chat");
45
+ },
46
+ format: (result) => "window" in result ? askedLine(result) : startedLines(result),
47
+ });
48
+ /**
49
+ * Starts the new game with the player's choices: those the AI asked in the chat, or the page's window gave (D-76). Only the chat's is refused on a game
50
+ * changed since it saw it (D-71): the player who presses New game on the page sees the game there (Federico, round of D-85).
51
+ */
52
+ export function startNewGame({ ai_colour: colour, from, clock, level: chosen, replace }, context, by) {
53
+ if (colour === undefined) {
54
+ throw new Error(from === undefined
55
+ ? "ai_colour: say the colour you play, white, black or random, with the other choices; or leave them all out for the page's window. Nothing changed."
56
+ : "ai_colour: say the colour you play from this position, white, black or random. Nothing changed.");
57
+ }
58
+ const control = clock === undefined || /^\s*none\s*$/i.test(clock) ? undefined : timeControlFrom(clock);
59
+ const now = context.now();
60
+ const folder = gameFolder(context.home());
61
+ const before = gameAt(folder, now);
62
+ const going = before !== undefined && endOf(before.game) === undefined ? before : undefined;
63
+ if (going !== undefined && !replace) {
64
+ const { moveNumber } = going.game.position;
65
+ throw new Error(`A game is going on, at move ${moveNumber}: end it with end_game, or call new_game with replace: true, which archives it unfinished. Nothing changed.`);
66
+ }
67
+ if (going !== undefined && by === "chat") {
68
+ refuseUnseenPosition(context, folder);
69
+ }
70
+ if (chatToClear(context)) {
71
+ throw new ChatNotClear();
72
+ }
73
+ const ai = colour === "random" ? (context.random() < 0.5 ? "white" : "black") : colour;
74
+ const level = chosen ?? levelToPlay(context, before);
75
+ const started = startOfNewGame(folder, now, before);
76
+ const game = startingGame(from, { ai, level }, started, control);
77
+ const current = control === undefined
78
+ ? { game, ai, started: started.toISOString(), talk: [] }
79
+ : { game, ai, started: started.toISOString(), talk: [], clock: startClock(control, now, context.pageOpen?.() === true) };
80
+ const archived = before === undefined || (going === undefined && archivedName(folder, before) !== undefined) ? undefined : archiveBefore(folder, before);
81
+ saveGame(folder, current);
82
+ endAnalysis(folder);
83
+ endTableTalk(folder);
84
+ const past = pastGameLines(folder, remembered);
85
+ const closed = closedPage(context);
86
+ const player = playerLine(context);
87
+ const speech = speechOf(folder, context.language);
88
+ if (closed !== undefined) {
89
+ pageGiven(context, current.started);
90
+ }
91
+ return {
92
+ game: withThought(summaryOf(current, now), folder, current, coachRuns(context)),
93
+ ...(archived === undefined ? {} : { archived: { name: archived, finished: going === undefined } }),
94
+ past,
95
+ ...(player === undefined ? {} : { player }),
96
+ ...(speech === undefined ? {} : { speech }),
97
+ ...(closed === undefined ? {} : { closedPage: closed }),
98
+ };
99
+ }
100
+ /** The new game's lines for the AI. */
101
+ function startedLines({ game, archived, past, player, speech, closedPage: closed }) {
102
+ return [
103
+ `New game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`,
104
+ ...(player === undefined ? [] : [player]),
105
+ ...(speech === undefined ? [] : [speechLine(speech)]),
106
+ ...(archived === undefined ? [] : [`The game before is in ${archived.name}${archived.finished ? "" : ", unfinished"}.`]),
107
+ ...(past.length === 0 ? [] : ["Last games with this player (past_games gives more, and their moves):", ...past]),
108
+ ...positionLines(game),
109
+ ...(closed === undefined ? [] : [pageLine(closed, true)]),
110
+ nextStep(game),
111
+ ].join("\n");
112
+ }
113
+ /**
114
+ * The New game window opened on the board page, or its address for the player to open it first; a chat to clear refuses it as a new game does (D-57), and
115
+ * without the page the player is asked in the chat.
116
+ */
117
+ function askedOnPage(context) {
118
+ if (chatToClear(context)) {
119
+ throw new ChatNotClear();
120
+ }
121
+ if (context.askNewGame?.() === true) {
122
+ return { window: "opened" };
123
+ }
124
+ if (context.page !== undefined) {
125
+ return { window: "closed", page: context.page };
126
+ }
127
+ throw new Error("There is no board page here: ask the player which colour they want, whether they want a clock and at which level you play, then call new_game with "
128
+ + "them. Nothing changed.");
129
+ }
130
+ function askedLine({ window, page }) {
131
+ return window === "opened"
132
+ ? "The New game window is open on the board page: the player chooses the game there. Don't ask in the chat; end your turn, the doorbell wakes you when "
133
+ + "the game starts."
134
+ : `Nobody has the board page open: give the player its address, ${page}, and tell them to start the game there with its New game button. End your turn.`;
135
+ }
136
+ /** The refusal of a new game in a chat that still holds the game before (D-57): the board page says it in the player's language. */
137
+ export class ChatNotClear extends Error {
138
+ constructor() {
139
+ super("The game before was played in this chat: ask the player to write /clear here, which empties the chat and keeps the games on disk, then "
140
+ + "start the new game. A clean chat plays faster and costs less. Nothing changed.");
141
+ }
142
+ }
143
+ /** How many of the last games new_game recalls. */
144
+ const remembered = 3;
145
+ /** From how many plies a game played in a chat asks for a clean one before the next (D-57): one left after a few moves does not. */
146
+ const pliesBeforeClearing = 20;
147
+ // A FEN's board: eight ranks of pieces and empty counts.
148
+ const fenPattern = /^[pnbrqkPNBRQK1-8]+(?:\/[pnbrqkPNBRQK1-8]+){7}(?:\s|$)/;
149
+ /**
150
+ * Whether the chat must be cleared first: the last game begun, in any profile, was played in it at some length, and the host says how chats start (the hook
151
+ * of Claude Code and Codex). Another profile does not clear the chat (D-66), nor a game another chat played, which this one was never shown (D-71).
152
+ */
153
+ function chatToClear(context) {
154
+ const start = lastChatStart(context.folder);
155
+ if (!hostsNotingChats.has(context.host?.() ?? "") || start === undefined) {
156
+ return false;
157
+ }
158
+ const last = gameFolders(context.home()).flatMap((folder) => loadGame(folder) ?? []).sort((one, other) => Date.parse(other.started) - Date.parse(one.started))[0];
159
+ return last !== undefined && last.started === context.positionSeen?.started && last.game.record.moves.length >= pliesBeforeClearing && !cleanSince(start, last.started);
160
+ }
161
+ // The hosts whose SessionStart hook, written by init, notes how each chat starts: Claude Code and Codex, by the names they give the server.
162
+ const hostsNotingChats = new Set(["claude-code", "codex-mcp-client"]);
163
+ function archiveBefore(folder, before) {
164
+ try {
165
+ return archiveGame(folder, before);
166
+ }
167
+ catch (error) {
168
+ throw new Error(`${error.message} Nothing changed.`, { cause: error });
169
+ }
170
+ }
171
+ /**
172
+ * The new game: the standard position, or the one a FEN or PGN gives, which must still be going on. Its tags are Chesspal's, the AI's level among them:
173
+ * an imported game gives its position only.
174
+ */
175
+ function startingGame(from, { ai, level }, started, control) {
176
+ const tags = {
177
+ Event: "Chesspal",
178
+ Date: pgnDate(started),
179
+ White: ai === "white" ? "AI" : "Player",
180
+ Black: ai === "black" ? "AI" : "Player",
181
+ [levelTag]: level,
182
+ ...(control === undefined ? {} : { TimeControl: timeControlTag(control) }),
183
+ };
184
+ let game;
185
+ try {
186
+ const record = from === undefined ? { tags: {}, moves: [] } : importedRecord(from.trim());
187
+ const fen = record.tags.FEN === undefined ? {} : { SetUp: "1", FEN: record.tags.FEN };
188
+ game = new Game({ tags: { ...tags, ...fen }, moves: record.moves });
189
+ }
190
+ catch (error) {
191
+ throw new Error(`from: ${error.message} Nothing changed.`);
192
+ }
193
+ const end = endOf(game);
194
+ if (end !== undefined) {
195
+ throw new Error(`from holds a game already over (${end.result}): give one still going on, or leave from out for a new game. Nothing changed.`);
196
+ }
197
+ return game;
198
+ }
199
+ function timeControlFrom(clock) {
200
+ try {
201
+ return timeControlOf(clock);
202
+ }
203
+ catch (error) {
204
+ throw new Error(`clock: ${error.message} Nothing changed.`);
205
+ }
206
+ }
207
+ function importedRecord(from) {
208
+ if (fenPattern.test(from)) {
209
+ return { tags: { SetUp: "1", FEN: from }, moves: [] };
210
+ }
211
+ const records = readPgn(from);
212
+ const [record] = records;
213
+ if (record === undefined || records.length > 1) {
214
+ throw new Error(`it holds ${records.length} games: give one, in PGN, or a FEN.`);
215
+ }
216
+ // Another game's [%plan] and [%mood] comments are not the AI's.
217
+ return withoutMoods(withoutPlans(record));
218
+ }
@@ -0,0 +1,13 @@
1
+ import { type GameRecord, type Opening } from "../core/index.ts";
2
+ /** The game's opening as the AI reads it: ECO and name, and the move it left the book at; none outside the book, or where the build made no index. */
3
+ export declare function openingLine(record: GameRecord): string | undefined;
4
+ /** The moves the book knows from a position (D-15), for the AI at the master level; none out of the book, or where the build made no book. */
5
+ export declare function knownMoves(fen: string): readonly string[];
6
+ /** How many plies from the start the book knows, to the last before the game left it (D-69); none from another position. */
7
+ export declare function bookPlies(record: GameRecord): number;
8
+ /** Whether a line of Lichess's openings reaches a position, by any order of moves (D-68); never where the build made no book. */
9
+ export declare function bookReaches(fen: string): boolean;
10
+ /** The opening a game reached, ECO and name, without where it left the book: for the games in the archive. */
11
+ export declare function openingName(record: GameRecord): string | undefined;
12
+ /** The last named opening a game reached, and at which ply; none outside the book, or where the build made no index. */
13
+ export declare function bookOpening(record: GameRecord): Opening | undefined;