chesspal 0.3.5 → 0.4.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 (191) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/cli.js +17 -4
  3. package/dist/cli/command-line.js +22 -12
  4. package/dist/cli/icon.d.ts +2 -0
  5. package/dist/cli/icon.js +11 -1
  6. package/dist/cli/prompt-hook.d.ts +17 -0
  7. package/dist/cli/prompt-hook.js +100 -0
  8. package/dist/commands/analysis/analyse.d.ts +2 -2
  9. package/dist/commands/analysis/analyse.js +10 -16
  10. package/dist/commands/analysis/annotate.d.ts +8 -0
  11. package/dist/commands/analysis/annotate.js +19 -3
  12. package/dist/commands/answers/agents.d.ts +17 -7
  13. package/dist/commands/answers/agents.js +46 -16
  14. package/dist/commands/answers/clock.js +2 -1
  15. package/dist/commands/answers/drawings.js +6 -2
  16. package/dist/commands/answers/index.d.ts +4 -3
  17. package/dist/commands/answers/index.js +4 -3
  18. package/dist/commands/answers/lines.d.ts +5 -3
  19. package/dist/commands/answers/lines.js +17 -9
  20. package/dist/commands/answers/opening-book.d.ts +13 -7
  21. package/dist/commands/answers/opening-book.js +34 -38
  22. package/dist/commands/answers/opening-tags.d.ts +4 -0
  23. package/dist/commands/answers/opening-tags.js +11 -0
  24. package/dist/commands/answers/play-move.d.ts +3 -1
  25. package/dist/commands/answers/play-move.js +9 -6
  26. package/dist/commands/answers/say.js +2 -1
  27. package/dist/commands/answers/summary.d.ts +7 -3
  28. package/dist/commands/answers/summary.js +16 -5
  29. package/dist/commands/answers/view.d.ts +1 -1
  30. package/dist/commands/answers/view.js +2 -2
  31. package/dist/commands/game/ai-move.js +4 -4
  32. package/dist/commands/game/coach.js +7 -6
  33. package/dist/commands/game/end.js +2 -2
  34. package/dist/commands/game/export.d.ts +1 -1
  35. package/dist/commands/game/index.d.ts +1 -1
  36. package/dist/commands/game/index.js +1 -1
  37. package/dist/commands/game/look.d.ts +7 -3
  38. package/dist/commands/game/look.js +9 -4
  39. package/dist/commands/game/new.js +42 -12
  40. package/dist/commands/game/player-move.d.ts +16 -0
  41. package/dist/commands/game/player-move.js +69 -19
  42. package/dist/commands/game/ponder.js +7 -4
  43. package/dist/commands/game/position.d.ts +2 -1
  44. package/dist/commands/game/position.js +9 -4
  45. package/dist/commands/game/status.d.ts +4 -2
  46. package/dist/commands/game/status.js +24 -14
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/index.js +3 -3
  49. package/dist/commands/profiles/past-games.d.ts +7 -2
  50. package/dist/commands/profiles/past-games.js +45 -18
  51. package/dist/commands/registry/context.d.ts +2 -0
  52. package/dist/commands/registry/context.js +5 -0
  53. package/dist/commands/registry/index.d.ts +1 -1
  54. package/dist/commands/registry/index.js +1 -1
  55. package/dist/commands/talk/ai-say.d.ts +1 -1
  56. package/dist/commands/talk/ai-say.js +7 -6
  57. package/dist/core/game.d.ts +13 -0
  58. package/dist/core/game.js +29 -3
  59. package/dist/core/index.d.ts +4 -4
  60. package/dist/core/index.js +3 -3
  61. package/dist/core/openings.d.ts +20 -6
  62. package/dist/core/openings.js +45 -22
  63. package/dist/core/pgn.d.ts +4 -0
  64. package/dist/core/pgn.js +35 -6
  65. package/dist/games/analysis.d.ts +7 -4
  66. package/dist/games/analysis.js +5 -3
  67. package/dist/games/archive.d.ts +25 -5
  68. package/dist/games/archive.js +52 -19
  69. package/dist/games/chat-start.d.ts +2 -2
  70. package/dist/games/chat-start.js +6 -1
  71. package/dist/games/current-game.d.ts +2 -2
  72. package/dist/games/current-game.js +3 -2
  73. package/dist/games/index.d.ts +3 -2
  74. package/dist/games/index.js +3 -2
  75. package/dist/games/position-shown.d.ts +24 -0
  76. package/dist/games/position-shown.js +51 -0
  77. package/dist/games/thoughts.d.ts +12 -4
  78. package/dist/games/thoughts.js +27 -10
  79. package/dist/home/index.d.ts +1 -0
  80. package/dist/home/index.js +1 -0
  81. package/dist/home/same-path.d.ts +7 -0
  82. package/dist/home/same-path.js +24 -0
  83. package/dist/host-files/host-files.d.ts +5 -0
  84. package/dist/host-files/host-files.js +21 -7
  85. package/dist/host-files/index.d.ts +1 -1
  86. package/dist/host-files/index.js +1 -1
  87. package/dist/page/app/anchor.js +15 -11
  88. package/dist/page/app/app.js +49 -14
  89. package/dist/page/app/served.js +30 -38
  90. package/dist/page/app/watch.js +97 -0
  91. package/dist/page/avatars/torretta.css +108 -0
  92. package/dist/page/base.css +1 -1
  93. package/dist/page/board/board.css +11 -8
  94. package/dist/page/board/marks.js +2 -2
  95. package/dist/page/board/pointer.js +2 -2
  96. package/dist/page/dialogs/analyses.js +5 -3
  97. package/dist/page/dialogs/dialogs.css +3 -1
  98. package/dist/page/dialogs/rows.js +72 -0
  99. package/dist/page/dialogs/site-games.js +3 -1
  100. package/dist/page/game/column.js +10 -8
  101. package/dist/page/game/game.css +137 -16
  102. package/dist/page/game/move-list.js +7 -5
  103. package/dist/page/game/plies.js +4 -0
  104. package/dist/page/game/review-progress.js +11 -0
  105. package/dist/page/guide/drawings/drawings.css +0 -1
  106. package/dist/page/guide/en.md +2 -2
  107. package/dist/page/guide/it.md +2 -2
  108. package/dist/page/helps/helps.css +5 -5
  109. package/dist/page/helps/helps.js +3 -181
  110. package/dist/page/helps/rows.js +16 -0
  111. package/dist/page/helps/strategy.js +105 -0
  112. package/dist/page/helps/tactics.js +69 -0
  113. package/dist/page/layout/header.js +49 -6
  114. package/dist/page/layout/icon.ico +0 -0
  115. package/dist/page/layout/layout.css +49 -17
  116. package/dist/page/layout/window.js +45 -6
  117. package/dist/page/lead/lead.js +41 -4
  118. package/dist/page/legend/legend.css +4 -16
  119. package/dist/page/legend/legend.js +28 -15
  120. package/dist/page/profile/settings.js +1 -0
  121. package/dist/page/sounds/sounds.js +1 -1
  122. package/dist/page/style.css +1 -0
  123. package/dist/page/table/action.js +42 -0
  124. package/dist/page/table/table.css +57 -39
  125. package/dist/page/table/window.js +67 -67
  126. package/dist/page/talk/talk.css +52 -118
  127. package/dist/page/talk/talk.js +65 -20
  128. package/dist/page/talk/voice.js +92 -21
  129. package/dist/page/text.js +34 -8
  130. package/dist/page/theme.css +13 -0
  131. package/dist/profiles/profiles.js +5 -1
  132. package/dist/server/bell/bell.d.ts +24 -29
  133. package/dist/server/bell/bell.js +79 -133
  134. package/dist/server/bell/index.d.ts +1 -1
  135. package/dist/server/bell/index.js +2 -2
  136. package/dist/server/mcp/mcp.js +49 -7
  137. package/dist/server/page/analyses.js +15 -4
  138. package/dist/server/page/chat.d.ts +5 -1
  139. package/dist/server/page/chat.js +5 -1
  140. package/dist/server/page/files.js +3 -1
  141. package/dist/server/page/game.d.ts +5 -0
  142. package/dist/server/page/game.js +95 -15
  143. package/dist/server/page/http.d.ts +1 -1
  144. package/dist/server/page/page.d.ts +14 -9
  145. package/dist/server/page/page.js +111 -26
  146. package/dist/server/page/table.d.ts +40 -5
  147. package/dist/server/page/table.js +65 -25
  148. package/dist/server/page/update.d.ts +1 -1
  149. package/dist/server/page/update.js +4 -4
  150. package/dist/server/page/windows.d.ts +22 -0
  151. package/dist/server/page/windows.js +45 -0
  152. package/dist/server/ports/index.d.ts +1 -0
  153. package/dist/server/ports/index.js +2 -0
  154. package/dist/server/ports/ports.d.ts +78 -0
  155. package/dist/server/ports/ports.js +222 -0
  156. package/dist/server/table/browser.d.ts +22 -6
  157. package/dist/server/table/browser.js +98 -20
  158. package/dist/server/table/hosts/chats.d.ts +5 -0
  159. package/dist/server/table/hosts/chats.js +11 -4
  160. package/dist/server/table/hosts/hooks.d.ts +6 -0
  161. package/dist/server/table/hosts/hooks.js +38 -0
  162. package/dist/server/table/hosts/hosts.d.ts +13 -5
  163. package/dist/server/table/hosts/hosts.js +16 -10
  164. package/dist/server/table/hosts/index.d.ts +1 -0
  165. package/dist/server/table/hosts/index.js +1 -0
  166. package/dist/server/table/hosts/play.d.ts +7 -3
  167. package/dist/server/table/hosts/play.js +63 -9
  168. package/dist/server/table/icon/index.d.ts +2 -0
  169. package/dist/server/table/icon/index.js +16 -2
  170. package/dist/server/table/index.d.ts +2 -2
  171. package/dist/server/table/index.js +1 -1
  172. package/dist/server/table/launch.d.ts +5 -4
  173. package/dist/server/table/launch.js +18 -5
  174. package/dist/server/table/link.js +4 -1
  175. package/dist/server/table/table.d.ts +7 -0
  176. package/dist/server/table/table.js +40 -12
  177. package/dist/sites/index.d.ts +1 -1
  178. package/dist/sites/index.js +1 -1
  179. package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
  180. package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
  181. package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
  182. package/dist/sites/{choice.js → ratings/choice.js} +1 -1
  183. package/dist/sites/ratings/index.d.ts +1 -0
  184. package/dist/sites/ratings/index.js +2 -0
  185. package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
  186. package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
  187. package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
  188. package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
  189. package/instructions/opponent.md +1 -1
  190. package/instructions/start.md +1 -1
  191. package/package.json +1 -1
@@ -6,9 +6,9 @@ interface Exported {
6
6
  /** The game as PGN or its position as FEN, for the player to copy or keep: the plans stay hidden while it goes on (D-29); the moves alone for another site. */
7
7
  export declare const exportGame: import("../registry/registry.ts").Command<z.ZodObject<{
8
8
  format: z.ZodEnum<{
9
- fen: "fen";
10
9
  moves: "moves";
11
10
  pgn: "pgn";
11
+ fen: "fen";
12
12
  }>;
13
13
  }, z.core.$strip>, Exported>;
14
14
  export {};
@@ -5,7 +5,7 @@ export { endGame } from "./end.ts";
5
5
  export { exportGame } from "./export.ts";
6
6
  export { look } from "./look.ts";
7
7
  export { ChatNotClear, newGame, startNewGame } from "./new.ts";
8
- export { playerMove } from "./player-move.ts";
8
+ export { type MoveSentAlone, playerMove, playMoveSentAlone } from "./player-move.ts";
9
9
  export { ponder } from "./ponder.ts";
10
10
  export { position } from "./position.ts";
11
11
  export { status } from "./status.ts";
@@ -7,7 +7,7 @@ export { endGame } from "./end.js";
7
7
  export { exportGame } from "./export.js";
8
8
  export { look } from "./look.js";
9
9
  export { ChatNotClear, newGame, startNewGame } from "./new.js";
10
- export { playerMove } from "./player-move.js";
10
+ export { playerMove, playMoveSentAlone } from "./player-move.js";
11
11
  export { ponder } from "./ponder.js";
12
12
  export { position } from "./position.js";
13
13
  export { status } from "./status.js";
@@ -24,11 +24,15 @@ export declare const look: import("../registry/registry.ts").Command<z.ZodObject
24
24
  }>>;
25
25
  }, z.core.$strip>, {
26
26
  board: string;
27
- } | {
27
+ } | ({
28
28
  game: Viewed;
29
29
  words: Written[];
30
- } | {
30
+ } & {
31
+ analyst?: string;
32
+ }) | ({
31
33
  study: StudyView;
32
34
  words: Written[];
33
- }>;
35
+ } & {
36
+ analyst?: string;
37
+ })>;
34
38
  export {};
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { boardText, materialOf, sees } from "../../core/index.js";
3
+ import { archivedName, endOf } from "../../games/index.js";
3
4
  import { analystAgent } from "../../host-files/index.js";
4
5
  import { gameFolder } from "../../profiles/index.js";
5
- import { fullView, gameAt, gameShown, headLines, materialText, newWords, positionAfter, positionLine, studyInput, summaryOf, wordLines } from "../answers/index.js";
6
+ import { analystOnView, fullView, gameAt, gameShown, headLines, materialText, newWords, positionAfter, positionLine, studyInput, summaryOf, wordLines } from "../answers/index.js";
6
7
  import { defineCommand } from "../registry/index.js";
7
8
  import { refuseUnseen } from "./levels.js";
8
9
  /** 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). */
@@ -34,13 +35,14 @@ export const look = defineCommand({
34
35
  }
35
36
  const { threats } = current.game;
36
37
  const viewed = { ...summaryOf(current, context.now()), facts: current.game.facts, possibilities: current.game.possibilities };
37
- return { game: threats === undefined ? viewed : { ...viewed, threats }, words: newWords(context) };
38
+ const over = endOf(current.game) === undefined ? undefined : archivedName(gameFolder(context.home()), current);
39
+ return withAnalystLine({ game: threats === undefined ? viewed : { ...viewed, threats }, words: newWords(context) }, analystOnView(context, over));
38
40
  },
39
41
  format: (look) => {
40
42
  if ("board" in look) {
41
43
  return look.board;
42
44
  }
43
- return [...("game" in look ? gameLines(look.game) : studyLines(look.study)), ...wordLines(look.words)].join("\n");
45
+ return [...("game" in look ? gameLines(look.game) : studyLines(look.study)), ...wordLines(look.words), ...(look.analyst === undefined ? [] : [look.analyst])].join("\n");
44
46
  },
45
47
  });
46
48
  /** A position of a game over, as the view of the game going on gives it. */
@@ -52,7 +54,10 @@ function studyLook(context, only, name, move) {
52
54
  return { board: boardText(position.fen) };
53
55
  }
54
56
  const view = { where: positionLine(at, studied.name), turn: position.turn, fen: position.fen, facts: at.game.facts, possibilities: at.game.possibilities, threats: at.game.threats };
55
- return { study: view, words: newWords(context) };
57
+ return withAnalystLine({ study: view, words: newWords(context) }, analystOnView(context, studied.name));
58
+ }
59
+ function withAnalystLine(look, analyst) {
60
+ return analyst === undefined ? look : { ...look, analyst };
56
61
  }
57
62
  /** The game going on: its head and the whole view, with the other side's chances from the strong level (D-100). */
58
63
  function gameLines(game) {
@@ -1,8 +1,11 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { z } from "zod";
2
4
  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";
5
+ import { archivedName, archiveGame, archiveName, cleanSince, endAnalysis, endOf, endTableTalk, lastChatStart, loadGame, saveGame, startOfNewGame, withoutPlans } from "../../games/index.js";
6
+ import { hasDoorbell } from "../../host-files/index.js";
4
7
  import { gameFolder, gameFolders } from "../../profiles/index.js";
5
- import { coachRuns, gameAt, nextStep, positionLines, refuseUnseenPosition, sidesLine, speechLine, speechOf, summaryOf, withThought } from "../answers/index.js";
8
+ import { coachRuns, gameAt, nextStep, openingTags, positionLines, refuseUnseenPosition, sidesLine, speechLine, speechOf, summaryOf, withThought } from "../answers/index.js";
6
9
  import { pastGameLines, playerLine } from "../profiles/index.js";
7
10
  import { defineCommand } from "../registry/index.js";
8
11
  import { levelText, levelToPlay } from "./levels.js";
@@ -37,7 +40,7 @@ export const newGame = defineCommand({
37
40
  }
38
41
  return startNewGame(input, context, "chat");
39
42
  },
40
- format: (result) => "window" in result ? askedLine(result) : startedLines(result),
43
+ format: (result, context) => "window" in result ? askedLine(result, hasDoorbell(context?.host?.())) : startedLines(result, hasDoorbell(context?.host?.())),
41
44
  });
42
45
  /**
43
46
  * 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
@@ -71,8 +74,7 @@ export function startNewGame({ ai_colour: colour, from, clock, level: chosen, re
71
74
  const current = control === undefined
72
75
  ? { game, ai, started: started.toISOString(), talk: [] }
73
76
  : { game, ai, started: started.toISOString(), talk: [], clock: startClock(control, now, context.pageOpen?.() === true) };
74
- const archived = before === undefined || (going === undefined && archivedName(folder, before) !== undefined) ? undefined : archiveBefore(folder, before);
75
- saveGame(folder, current);
77
+ const archived = replaceGame(folder, current, before, going !== undefined);
76
78
  endAnalysis(folder);
77
79
  endTableTalk(folder);
78
80
  const past = pastGameLines(folder, remembered);
@@ -92,16 +94,16 @@ export function startNewGame({ ai_colour: colour, from, clock, level: chosen, re
92
94
  };
93
95
  }
94
96
  /** The new game's lines for the AI. */
95
- function startedLines({ game, archived, past, player, speech, closedPage: closed }) {
97
+ function startedLines({ game, archived, past, player, speech, closedPage: closed }, doorbell) {
96
98
  return [
97
99
  `New game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`,
98
100
  ...(player === undefined ? [] : [player]),
99
101
  ...(speech === undefined ? [] : [speechLine(speech)]),
100
- ...(archived === undefined ? [] : [`The game before is in ${archived.name}${archived.finished ? "" : ", unfinished"}.`]),
102
+ ...(archived === undefined ? [] : [`The game before is saved with the player's games${archived.finished ? "" : ", unfinished"}.`]),
101
103
  ...(past.length === 0 ? [] : ["Last games with this player (past_games gives more, and their moves):", ...past]),
102
104
  ...positionLines(game),
103
105
  ...(closed === undefined ? [] : [pageLine(closed, true)]),
104
- nextStep(game),
106
+ nextStep(game, [], false, doorbell),
105
107
  ].join("\n");
106
108
  }
107
109
  /**
@@ -131,11 +133,16 @@ function justBegun(context) {
131
133
  const current = gameAt(gameFolder(context.home()), context.now());
132
134
  return current !== undefined && endOf(current.game) === undefined && current.game.record.moves.length === 0;
133
135
  }
134
- function askedLine({ window, page }) {
135
- return window === "opened"
136
+ function askedLine({ window, page }, doorbell) {
137
+ // A host with no doorbell hears of the game from the player, in its window.
138
+ const told = "End your turn, and call status when they tell you the game started.";
139
+ if (window !== "opened") {
140
+ return `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. ${doorbell ? "End your turn." : told}`;
141
+ }
142
+ return doorbell
136
143
  ? "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 "
137
144
  + "the game starts."
138
- : `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.`;
145
+ : `The New game window is open on the board page: the player chooses the game there. Don't ask in the chat. ${told}`;
139
146
  }
140
147
  /** 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. */
141
148
  export class ChatNotClear extends Error {
@@ -164,9 +171,32 @@ function chatToClear(context) {
164
171
  }
165
172
  // The hosts whose SessionStart hook, written by init, notes how each chat starts: Claude Code and Codex, by the names they give the server.
166
173
  const hostsNotingChats = new Set(["claude-code", "codex-mcp-client"]);
174
+ /**
175
+ * Writes the new game over the one before, archived first: both under game.json's lock, on the game before as read, so a game another process changed
176
+ * since is neither archived stale nor lost. When game.json cannot be written, an archive this call made goes: nothing changed.
177
+ */
178
+ function replaceGame(folder, current, before, going) {
179
+ let archived;
180
+ let made = false;
181
+ try {
182
+ saveGame(folder, current, () => {
183
+ if (before !== undefined && (going || archivedName(folder, before) === undefined)) {
184
+ made = !existsSync(join(folder, archiveName(before)));
185
+ archived = archiveBefore(folder, before);
186
+ }
187
+ });
188
+ }
189
+ catch (error) {
190
+ if (made && archived !== undefined) {
191
+ rmSync(join(folder, archived), { force: true });
192
+ }
193
+ throw error;
194
+ }
195
+ return archived;
196
+ }
167
197
  function archiveBefore(folder, before) {
168
198
  try {
169
- return archiveGame(folder, before);
199
+ return archiveGame(folder, before, openingTags(before.game.record));
170
200
  }
171
201
  catch (error) {
172
202
  throw new Error(`${error.message} Nothing changed.`, { cause: error });
@@ -1,10 +1,26 @@
1
1
  import { z } from "zod";
2
2
  import { type MovePlayed } from "../answers/index.ts";
3
+ import { type Context, type SeenPosition } from "../registry/index.ts";
3
4
  /** The player's move, written in the chat and given by the AI: it answers with the position the AI decides on (D-34). */
4
5
  export declare const playerMove: import("../registry/registry.ts").Command<z.ZodObject<{
5
6
  move: z.ZodString;
6
7
  offer_draw: z.ZodOptional<z.ZodBoolean>;
7
8
  }, z.core.$strip>, PlayerMove>;
9
+ /**
10
+ * A move the player sent alone in the chat, played by Chesspal before the AI reads it: what the AI is told, the position it then sees, the stamp of
11
+ * game.json after it, and the keys of the player's words on the board page given with it, for the chat's server to count them as given.
12
+ */
13
+ export interface MoveSentAlone {
14
+ answer: string;
15
+ position: SeenPosition;
16
+ stamp?: string;
17
+ given: string[];
18
+ }
19
+ /**
20
+ * Plays the move the player sent alone in the chat, before the AI reads it (D-36): at the moment they sent it, so with no allowance, and answered as
21
+ * player_move answers, with the player's words on the board page the AI has not answered. Refused as player_move refuses it: the AI reads the prompt then.
22
+ */
23
+ export declare function playMoveSentAlone(move: string, context: Context): MoveSentAlone;
8
24
  /** The player's move, with the board page's address once a game while no browser has it open (D-85), and the coach to start when it ended the game (D-88). */
9
25
  interface PlayerMove extends MovePlayed {
10
26
  invite?: string;
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- import { loadGame } from "../../games/index.js";
2
+ import { withSanPieces } from "../../core/index.js";
3
+ import { endOf, gameStamp, loadGame, rerunIfChanged } from "../../games/index.js";
4
+ import { hasDoorbell } from "../../host-files/index.js";
3
5
  import { gameFolder } from "../../profiles/index.js";
4
6
  import { closingLines, coachOnEnd, coachOnEndLine, coachRuns, lastMoveText, moveLines, newWords, playMove, positionIn, refuseUnseenPosition, shownMove, withAnalyst } from "../answers/index.js";
5
7
  import { defineCommand } from "../registry/index.js";
@@ -17,30 +19,78 @@ export const playerMove = defineCommand({
17
19
  alwaysLoad: true,
18
20
  showsGame: true,
19
21
  positionals: ["move"],
20
- run: ({ move, offer_draw: offerDraw = false }, context) => {
21
- const folder = gameFolder(context.home());
22
- // A chat that has seen no game yet, new or resumed, plays the player's own move: its answer shows the game the AI decides on (D-71).
23
- if (context.positionSeen !== null) {
24
- refuseUnseenPosition(context, folder);
25
- }
26
- const started = context.positionSeen === null ? positionIn(folder).started : context.positionSeen?.started;
27
- const played = playMove(folder, context.now(), "player", move, (message) => `${message} Ask the player again. Nothing changed.`, { allowance: chatAllowance, offerDraw, coached: coachRuns(context) });
28
- // A game over by this move has no more moves to play there: the next game's window gives the address (Federico, round of D-85).
29
- const invite = played.game.end === undefined ? pageInvite(context, started) : undefined;
30
- const ended = played.game.end === undefined ? undefined : loadGame(folder);
31
- const coach = ended !== undefined && coachOnEnd(context, folder, ended);
32
- return { ...played, game: withAnalyst(played.game, context), words: newWords(context), ...(invite === undefined ? {} : { invite }), ...(coach ? { coach } : {}) };
33
- },
34
- format: (result) => [
22
+ run: (input, context) => playersMove(input, context, chatAllowance),
23
+ format: (result, context) => [
35
24
  `Player: ${result.game.lastMove === undefined ? shownMove(result.move) : lastMoveText(result.game.lastMove)}.`,
36
25
  ...moveLines(result),
37
26
  ...(result.invite === undefined ? [] : [inviteLine(result.invite)]),
38
- ...(result.coach === true ? [coachOnEndLine] : []),
39
- ...closingLines(result.game, result.words),
27
+ ...(result.coach === true ? [coachOnEndLine(result.game)] : []),
28
+ ...closingLines(result.game, result.words, false, hasDoorbell(context?.host?.())),
40
29
  ].join("\n"),
41
30
  });
31
+ /**
32
+ * Plays the move the player sent alone in the chat, before the AI reads it (D-36): at the moment they sent it, so with no allowance, and answered as
33
+ * player_move answers, with the player's words on the board page the AI has not answered. Refused as player_move refuses it: the AI reads the prompt then.
34
+ */
35
+ export function playMoveSentAlone(move, context) {
36
+ const folder = gameFolder(context.home());
37
+ const before = new Set(context.given);
38
+ const played = rerunIfChanged(() => playersMove({ move }, context, 0));
39
+ const stamp = gameStamp(folder);
40
+ return {
41
+ answer: `Chesspal has already played the move the player wrote alone in the chat: don't call player_move for it.\n${playerMove.format(played, context)}`,
42
+ position: positionIn(folder),
43
+ ...(stamp === undefined ? {} : { stamp }),
44
+ given: [...context.given].filter((key) => !before.has(key)),
45
+ };
46
+ }
42
47
  /**
43
48
  * The time a move in the chat leaves off the player's clock (D-36): the AI writing after its move and reading the player's, about 10 s as Priya measured.
44
- * Claude Code's hooks will time it exactly; the board page's moves need none.
49
+ * The board page's moves need none, nor those Chesspal plays as the player sends them.
45
50
  */
46
51
  const chatAllowance = 10_000;
52
+ function playersMove({ move, offer_draw: offerDraw = false }, context, allowance) {
53
+ const folder = gameFolder(context.home());
54
+ // A chat that has seen no game yet, new or resumed, plays the player's own move: its answer shows the game the AI decides on (D-71).
55
+ if (context.positionSeen !== null) {
56
+ refuseUnseenPosition(context, folder);
57
+ }
58
+ refuseKingAsRook(move, folder);
59
+ // A piece's letter of another language is read as SAN's (D-113): Cf3 is the knight's move, Tf1 the rook's.
60
+ const read = withSanPieces(move);
61
+ const readAs = read === move ? "" : `${move.trim()} is read as ${read.trim()}. `;
62
+ const started = context.positionSeen === null ? positionIn(folder).started : context.positionSeen?.started;
63
+ const played = playMove(folder, context.now(), "player", read, (message) => `${readAs}${message} Ask the player again. Nothing changed.`, { allowance, offerDraw, coached: coachRuns(context) });
64
+ // A game over by this move has no more moves to play there: the next game's window gives the address (Federico, round of D-85).
65
+ const invite = played.game.end === undefined ? pageInvite(context, started) : undefined;
66
+ const ended = played.game.end === undefined ? undefined : loadGame(folder);
67
+ const coach = ended !== undefined && coachOnEnd(context, folder, ended);
68
+ return { ...played, game: withAnalyst(played.game, context), words: newWords(context), ...(invite === undefined ? {} : { invite }), ...(coach ? { coach } : {}) };
69
+ }
70
+ /**
71
+ * Refuses a move written R and a square when the player's king could go there: R is the king in Italian, French and Spanish, and only the player's words
72
+ * tell which. A rook's move that names its file or rank, or given as squares, is never the king's.
73
+ */
74
+ function refuseKingAsRook(move, folder) {
75
+ const written = move.trim().replace(/^\d+\s*(?:\.[\s.]*|…\s*)/, "").replace(/\s+/g, "");
76
+ const target = /^[Rr][x:]?([a-h][1-8])[+#]*[!?]*$/.exec(written)?.[1];
77
+ const current = target === undefined ? undefined : loadGame(folder);
78
+ // Another side to move, or a game over, is refused by the move itself.
79
+ if (current === undefined || current.game.position.turn === current.ai || endOf(current.game) !== undefined) {
80
+ return;
81
+ }
82
+ const there = current.game.legalMoves().filter((legal) => legal.to === target);
83
+ const rooks = there.filter((legal) => legal.san.startsWith("R")).map(squaresOf);
84
+ const king = there.find((legal) => legal.san.startsWith("K"));
85
+ if (rooks.length > 0 && king !== undefined) {
86
+ throw new Error(`${written} may be the rook's move, ${rooks.join(" or ")}, or the king's, ${squaresOf(king)}: R is the king in Italian, French and Spanish. `
87
+ + "Unless the player's words name the piece, ask them which; then give it as squares. Nothing changed.");
88
+ }
89
+ if (rooks.length === 0 && king !== undefined) {
90
+ throw new Error(`${written} is not legal: R is the rook in SAN, and no rook can go to ${target}. The king can, ${king.san}, and R is the king in Italian, French and Spanish: `
91
+ + `ask the player whether they meant the king, and give ${king.san} only once they say so. Nothing changed.`);
92
+ }
93
+ }
94
+ function squaresOf(move) {
95
+ return `${move.from}${move.to}`;
96
+ }
@@ -26,7 +26,10 @@ export const ponder = defineCommand({
26
26
  }),
27
27
  byAgents: ponderAgents,
28
28
  hosts: agentHosts,
29
- run: async ({ position, reply, move, why, say, done = false }, context) => {
29
+ run: async (input, context) => {
30
+ const { reply, move, why, say, done = false } = input;
31
+ // A quick model writes ponder {} as ponder {position: 0}, and was told to stop: alone, it asks for the position too (Priya).
32
+ const position = input.position === 0 && Object.keys(input).length === 1 ? undefined : input.position;
30
33
  const folder = gameFolder(context.home());
31
34
  const current = toPonder(folder, context.now(), position);
32
35
  if (current === undefined) {
@@ -36,7 +39,7 @@ export const ponder = defineCommand({
36
39
  if (reply !== undefined || move !== undefined || why !== undefined || say !== undefined || done) {
37
40
  throw new Error("Give the position's number with it: call ponder {} first, which gives it. Nothing changed.");
38
41
  }
39
- notePondering(folder, thoughtKey(current), true);
42
+ notePondering(folder, thoughtKey(current), true, context.now());
40
43
  return { start: startOf(current) };
41
44
  }
42
45
  if ((say !== undefined || done) && (reply !== undefined || move !== undefined || why !== undefined || (say !== undefined && done))) {
@@ -47,7 +50,7 @@ export const ponder = defineCommand({
47
50
  return { said: position };
48
51
  }
49
52
  if (done) {
50
- notePondering(folder, thoughtKey(current), false);
53
+ notePondering(folder, thoughtKey(current), false, context.now());
51
54
  return await waitForMove(folder, context, current);
52
55
  }
53
56
  if (reply === undefined) {
@@ -71,7 +74,7 @@ export const ponder = defineCommand({
71
74
  if (!answer.played) {
72
75
  throw new Error(`${answer.message} Your legal moves after ${played.move.san}: ${answer.legal.join(" ")}. Nothing changed.`);
73
76
  }
74
- return { kept: keepThought(folder, thoughtKey(current), { reply: played.move.san, move: answer.move.san, why }), position };
77
+ return { kept: keepThought(folder, thoughtKey(current), { reply: played.move.san, move: answer.move.san, why }, context.now()), position };
75
78
  },
76
79
  format: (pondered) => {
77
80
  if ("stop" in pondered) {
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { type Profile } from "../../core/index.ts";
3
- /** The positional profile of a position: of a game studied, with where it stands. */
3
+ /** The positional profile of a position: of a game studied, with where it stands; with the analyst's line for a game in analysis without its review. */
4
4
  type Described = Profile & {
5
5
  where?: string;
6
+ analyst?: string;
6
7
  };
7
8
  /** The positional profile of the game in progress (D-19), or of any position of a game over (D-68): separate facts, never a score. */
8
9
  export declare const position: import("../registry/registry.ts").Command<z.ZodObject<{
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import {} from "../../core/index.js";
3
+ import { archivedName, endOf } from "../../games/index.js";
3
4
  import { analystAgent } from "../../host-files/index.js";
4
5
  import { gameFolder } from "../../profiles/index.js";
5
- import { colourName, gameAt, gameShown, materialText, positionAfter, positionLine, studyInput } from "../answers/index.js";
6
+ import { analystOnView, colourName, gameAt, gameShown, materialText, positionAfter, positionLine, studyInput } from "../answers/index.js";
6
7
  import { defineCommand } from "../registry/index.js";
7
8
  import { refuseUnseen } from "./levels.js";
8
9
  /** The positional profile of the game in progress (D-19), or of any position of a game over (D-68): separate facts, never a score. */
@@ -21,17 +22,21 @@ export const position = defineCommand({
21
22
  if (game !== undefined || move !== undefined) {
22
23
  const studied = gameShown(context, game);
23
24
  const at = positionAfter(studied.record, move);
24
- return { ...at.game.profile, where: positionLine(at, studied.name) };
25
+ return withAnalystLine({ ...at.game.profile, where: positionLine(at, studied.name) }, analystOnView(context, studied.name));
25
26
  }
26
27
  const current = gameAt(gameFolder(context.home()), context.now());
27
28
  if (current === undefined) {
28
29
  throw new Error("There is no game to describe: start one with new_game.");
29
30
  }
30
31
  refuseUnseen(current, "strategy", "position");
31
- return current.game.profile;
32
+ const over = endOf(current.game) === undefined ? undefined : archivedName(gameFolder(context.home()), current);
33
+ return withAnalystLine(current.game.profile, analystOnView(context, over));
32
34
  },
33
- format: (described) => [...(described.where === undefined ? [] : [described.where]), ...profileLines(described)].join("\n"),
35
+ format: (described) => [...(described.where === undefined ? [] : [described.where]), ...profileLines(described), ...(described.analyst === undefined ? [] : [described.analyst])].join("\n"),
34
36
  });
37
+ function withAnalystLine(described, analyst) {
38
+ return analyst === undefined ? described : { ...described, analyst };
39
+ }
35
40
  /** The profile as lines for the AI, White's facts before Black's; a line with nothing to say is left out. */
36
41
  export function profileLines(profile) {
37
42
  const sides = [["white", profile.white], ["black", profile.black]];
@@ -16,16 +16,18 @@ interface Status {
16
16
  problem?: string;
17
17
  /** What the player wrote on the board page that the AI has not answered yet, all of it. */
18
18
  words?: Written[];
19
- /** The game in analysis on the board page once none goes on (D-69), which the player may have brought there. */
19
+ /** The game in analysis on the board page once none goes on (D-69), which the player may have brought there, for a new review too. */
20
20
  analysis?: {
21
21
  game: string;
22
22
  brought?: true;
23
+ redo?: true;
23
24
  analyst?: "done" | "offer";
25
+ codex?: true;
24
26
  review?: string;
25
27
  };
26
28
  /** A game going on from before this chat, told once a chat: the player says whether to go on with it. */
27
29
  fromBefore?: true;
28
- /** The coach to start, once, for the player's move that ended the game (D-88). */
30
+ /** The coach to start, once a move ended the game, for the player's last move (D-88). */
29
31
  coach?: true;
30
32
  /** The files an older init wrote in the player's folder, which this version would write otherwise. */
31
33
  outdated?: string[];
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { gameReviewOf, leadOf, reviewOf } from "../../core/index.js";
2
+ import { Game, gameReviewOf, leadOf, reviewOf } from "../../core/index.js";
3
3
  import { archivedName, cleanSince, endOf, gameInAnalysis, lastChatStart } from "../../games/index.js";
4
- import { outdatedHostFiles } from "../../host-files/index.js";
4
+ import { hasDoorbell, isCodex, outdatedHostFiles } from "../../host-files/index.js";
5
5
  import { gameFolder } from "../../profiles/index.js";
6
6
  import { version } from "../../version.js";
7
7
  import { analystLine, analystRuns, closingLines, coachOnEnd, coachOnEndLine, coachRuns, gameAt, goneOver, namedGame, newGameStep, positionLines, reviewedLine, shownMoves, sidesLine, speechLine, speechOf, summaryOf, unansweredWords, withAnalyst, withThought, wordLines, } from "../answers/index.js";
@@ -56,7 +56,7 @@ export const status = defineCommand({
56
56
  if (first && endOf(current.game) === undefined && current.game.record.moves.length > 0 && (chatStart === undefined || cleanSince(chatStart, current.started))) {
57
57
  status.fromBefore = true;
58
58
  }
59
- // The player's move on the board page that ended the game: the coach judges it too (D-88).
59
+ // A move that ended the game: the coach judges the player's last move, told until it starts (D-88).
60
60
  if (coachOnEnd(context, folder, current)) {
61
61
  status.coach = true;
62
62
  }
@@ -68,7 +68,9 @@ export const status = defineCommand({
68
68
  status.analysis = {
69
69
  game: analysis.game,
70
70
  ...(analysis.brought === true ? { brought: true } : {}),
71
+ ...(analysis.redo === true ? { redo: true } : {}),
71
72
  ...(analystRuns(context) ? { analyst: goneOver(context, analysis.game) ? "done" : "offer" } : {}),
73
+ ...(analystRuns(context) && isCodex(context.host?.()) ? { codex: true } : {}),
72
74
  ...(review === undefined ? {} : { review }),
73
75
  };
74
76
  }
@@ -78,7 +80,7 @@ export const status = defineCommand({
78
80
  }
79
81
  return status;
80
82
  },
81
- format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated, unread }) => [
83
+ format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated, unread }, context) => [
82
84
  `Chesspal ${version}. Folder: ${folder}`,
83
85
  ...(unread === true ? [unreadLine] : []),
84
86
  ...(outdated === undefined ? [] : [outdatedLine(outdated)]),
@@ -86,7 +88,7 @@ export const status = defineCommand({
86
88
  ...(player === undefined ? [] : [player]),
87
89
  ...(speech === undefined ? [] : [speechLine(speech)]),
88
90
  ...(fromBefore === true ? [fromBeforeLine] : []),
89
- ...(problem !== undefined ? [problem] : gameLines(game, words, analysis?.brought === true, pageClosed === true, coach === true)),
91
+ ...(problem !== undefined ? [problem] : gameLines(game, words, analysis?.brought === true, pageClosed === true, coach === true, hasDoorbell(context?.host?.()))),
90
92
  ...(analysis === undefined ? [] : [analysisLine(analysis)]),
91
93
  ].join("\n"),
92
94
  });
@@ -113,30 +115,38 @@ function firstInChat(context, chatStart) {
113
115
  }
114
116
  /**
115
117
  * The game in analysis, with how far its review has got; the next step when the player has just brought it on the board page, unless an analyst is at it
116
- * already; the analyst agent for a game whose review was deleted, where it runs (Stefano).
118
+ * already, at once when they asked for a new review (Priya); the analyst agent for a game whose review was deleted, where it runs (Stefano).
117
119
  */
118
- function analysisLine({ game, brought, analyst, review }) {
120
+ function analysisLine({ game, brought, redo, analyst, codex, review }) {
121
+ // A chat that remembered the old review put its notes back with annotate_game, and its corrections stand over every review after (Priya).
122
+ if (redo === true && analyst === "offer") {
123
+ return `The player asked on the board page for a new review of ${game}. Next, at once, even if an analyst was stopped before: ${analystLine(game, codex === true)} `
124
+ + "The new review replaces the old one: correct a note with annotate_game only when the player shows it wrong.";
125
+ }
119
126
  if (brought === true && analyst !== "done") {
120
127
  return analyst === "offer"
121
- ? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game)}`
128
+ ? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game, codex === true)}`
122
129
  : `The player brought ${game} on the board page to analyse it. Next: call analyse_game {"game": "${game}"}, then keep your review on that page with annotate_game, and go over it with them.`;
123
130
  }
124
131
  const shown = `In analysis on the board page: ${game}${review === undefined ? "" : ` (${review})`}`;
125
132
  if (analyst === "done") {
126
133
  return `${shown}. ${reviewedLine(game)}`;
127
134
  }
128
- return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
135
+ return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game, codex === true)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
129
136
  }
130
137
  /**
131
- * How far the review of a game has got, as the board page shows it (Priya): done once it holds the word on the whole game and who is ahead after every move
132
- * but the last; the analyst may still write notes after its word, so that alone is not the end.
138
+ * How far the review of a game has got, as the board page shows it (Priya): done once it holds the word on the whole game and who is ahead after every move,
139
+ * but the last when the board ended the game there, which says it; the analyst may still write notes after its word, so that alone is not the end.
133
140
  */
134
141
  function reviewProgress(context, game) {
135
142
  try {
136
143
  const { record } = namedGame(context, game);
137
144
  const worded = gameReviewOf(record) !== undefined;
138
145
  const estimated = record.moves.filter(({ comment }) => leadOf(comment) !== undefined).length;
139
- if (worded && estimated >= record.moves.length - 1) {
146
+ // A game resigned or lost on time needs who is ahead after its last move too; the game is played over only when that is the one estimate missing.
147
+ const all = record.moves.length;
148
+ const done = estimated >= all || (estimated === all - 1 && new Game(record).outcome.over);
149
+ if (worded && done) {
140
150
  return "its review is done";
141
151
  }
142
152
  const noted = record.moves.filter(({ comment }) => {
@@ -156,8 +166,8 @@ function reviewProgress(context, game) {
156
166
  * The game's lines; a game the player brought to analyse takes the place of the next step. The coach to start goes just before the next step, where
157
167
  * the AI acts on it (Priya).
158
168
  */
159
- function gameLines(game, words, brought, pageClosed, coach) {
160
- const closing = [...(coach ? [coachOnEndLine] : []), ...(brought ? wordLines(words) : closingLines(game, words, pageClosed))];
169
+ function gameLines(game, words, brought, pageClosed, coach, doorbell) {
170
+ const closing = [...(coach && game !== undefined ? [coachOnEndLine(game)] : []), ...(brought ? wordLines(words) : closingLines(game, words, pageClosed, doorbell))];
161
171
  if (game === undefined) {
162
172
  return ["No game.", ...closing];
163
173
  }
@@ -1,8 +1,8 @@
1
1
  import type { AnyCommand } from "./registry/index.ts";
2
2
  export { analyseGame, deleteReview, siteGames, SiteGamesRefused } from "./analysis/index.ts";
3
- export { analystOf, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.ts";
4
- export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, newGame, offerDraw, startNewGame } from "./game/index.ts";
3
+ export { analystOf, analystQuietAt, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.ts";
4
+ export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, type MoveSentAlone, newGame, offerDraw, playMoveSentAlone, startNewGame } from "./game/index.ts";
5
5
  export { chooseDrawnAvatar, deleteGame, editProfile, GameGoingOn, newProfile, ratingsAge, refreshRatings, useProfile } from "./profiles/index.ts";
6
- export { type AnyCommand, baseType, type Caller, type Command, commandLineFieldProblem, type Context, defaultContext, inputProblems, isBoolean, isList, type SeenPosition, speaksItalian, takesNumbers } from "./registry/index.ts";
6
+ export { type AnyCommand, baseType, type Caller, type Command, commandLineFieldProblem, type Context, defaultContext, homeForOneCall, inputProblems, isBoolean, isList, type SeenPosition, speaksItalian, takesNumbers, } from "./registry/index.ts";
7
7
  /** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
8
8
  export declare const commands: AnyCommand[];
@@ -6,10 +6,10 @@ import { init, instructions } from "./setup/index.js";
6
6
  import { aiSay, playerSay } from "./talk/index.js";
7
7
  // 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.
8
8
  export { analyseGame, deleteReview, siteGames, SiteGamesRefused } from "./analysis/index.js";
9
- export { analystOf, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.js";
10
- export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, newGame, offerDraw, startNewGame } from "./game/index.js";
9
+ export { analystOf, analystQuietAt, bookOpening, bookPlies, gameAt, gameToStudy, namedGame, playMove, PositionChanged, positionIn, samePosition, say, shownPosition } from "./answers/index.js";
10
+ export { aiMove, answerDraw, ChatNotClear, DrawRefused, endGame, newGame, offerDraw, playMoveSentAlone, startNewGame } from "./game/index.js";
11
11
  export { chooseDrawnAvatar, deleteGame, editProfile, GameGoingOn, newProfile, ratingsAge, refreshRatings, useProfile } from "./profiles/index.js";
12
- export { baseType, commandLineFieldProblem, defaultContext, inputProblems, isBoolean, isList, speaksItalian, takesNumbers } from "./registry/index.js";
12
+ export { baseType, commandLineFieldProblem, defaultContext, homeForOneCall, inputProblems, isBoolean, isList, speaksItalian, takesNumbers, } from "./registry/index.js";
13
13
  /** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
14
14
  export const commands = [
15
15
  init,
@@ -1,11 +1,16 @@
1
1
  import { z } from "zod";
2
2
  /** The AI's memory of the games played (D-57): how the latest went, one line each, or one game whole from the archive. */
3
3
  export declare const pastGames: import("../registry/registry.ts").Command<z.ZodObject<{
4
+ search: z.ZodOptional<z.ZodString>;
4
5
  game: z.ZodOptional<z.ZodString>;
5
6
  }, z.core.$strip>, {
6
7
  games: string[];
8
+ searched: true | undefined;
7
9
  } | {
8
10
  pgn: string;
9
11
  }>;
10
- /** The latest games of the archive, one line each as the AI reads them: none when there is no archive. A game closed before any move is no game played. */
11
- export declare function pastGameLines(folder: string, count: number): string[];
12
+ /**
13
+ * The latest games of the archive, one line each as the AI reads them, those holding every word searched: none when there is no archive. A game closed
14
+ * before any move is no game played. A game is read from its tags alone when the archive wrote the opening's and the plies', or else whole.
15
+ */
16
+ export declare function pastGameLines(folder: string, count: number, search?: string): string[];