chesspal 0.3.1 → 0.3.3

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 (185) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/cli.js +6 -6
  3. package/dist/commands/{analyse-game.d.ts → analysis/analyse.d.ts} +2 -2
  4. package/dist/commands/{analyse-game.js → analysis/analyse.js} +7 -11
  5. package/dist/commands/{annotate-game.d.ts → analysis/annotate.d.ts} +1 -1
  6. package/dist/commands/{annotate-game.js → analysis/annotate.js} +6 -9
  7. package/dist/commands/{delete-review.d.ts → analysis/delete-review.d.ts} +1 -1
  8. package/dist/commands/{delete-review.js → analysis/delete-review.js} +5 -5
  9. package/dist/commands/analysis/index.d.ts +4 -0
  10. package/dist/commands/analysis/index.js +5 -0
  11. package/dist/commands/{study-games.d.ts → analysis/study-games.d.ts} +1 -1
  12. package/dist/commands/{study-games.js → analysis/study-games.js} +4 -5
  13. package/dist/commands/{agents.d.ts → answers/agents.d.ts} +4 -4
  14. package/dist/commands/{agents.js → answers/agents.js} +13 -9
  15. package/dist/commands/{clock.d.ts → answers/clock.d.ts} +2 -2
  16. package/dist/commands/{clock.js → answers/clock.js} +2 -2
  17. package/dist/commands/{drawings.d.ts → answers/drawings.d.ts} +1 -1
  18. package/dist/commands/{drawings.js → answers/drawings.js} +1 -1
  19. package/dist/commands/answers/index.d.ts +12 -0
  20. package/dist/commands/answers/index.js +14 -0
  21. package/dist/commands/{lead-input.d.ts → answers/lead-input.d.ts} +1 -1
  22. package/dist/commands/{lead-input.js → answers/lead-input.js} +1 -1
  23. package/dist/commands/{answer-lines.d.ts → answers/lines.d.ts} +2 -2
  24. package/dist/commands/{answer-lines.js → answers/lines.js} +4 -4
  25. package/dist/commands/{opening-book.d.ts → answers/opening-book.d.ts} +1 -1
  26. package/dist/commands/{opening-book.js → answers/opening-book.js} +3 -3
  27. package/dist/commands/{play-move.d.ts → answers/play-move.d.ts} +2 -2
  28. package/dist/commands/{play-move.js → answers/play-move.js} +3 -3
  29. package/dist/commands/{say.d.ts → answers/say.d.ts} +3 -3
  30. package/dist/commands/{say.js → answers/say.js} +4 -4
  31. package/dist/commands/{speech.js → answers/speech.js} +1 -1
  32. package/dist/commands/{study.d.ts → answers/study.d.ts} +3 -3
  33. package/dist/commands/{study.js → answers/study.js} +3 -3
  34. package/dist/commands/{game-summary.d.ts → answers/summary.d.ts} +2 -2
  35. package/dist/commands/{game-summary.js → answers/summary.js} +4 -4
  36. package/dist/commands/{view.d.ts → answers/view.d.ts} +1 -1
  37. package/dist/commands/{ai-move.d.ts → game/ai-move.d.ts} +2 -2
  38. package/dist/commands/{ai-move.js → game/ai-move.js} +6 -11
  39. package/dist/commands/{coach.d.ts → game/coach.d.ts} +1 -1
  40. package/dist/commands/{coach.js → game/coach.js} +7 -13
  41. package/dist/commands/{draw-offer.d.ts → game/draw-offer.d.ts} +1 -1
  42. package/dist/commands/{draw-offer.js → game/draw-offer.js} +4 -4
  43. package/dist/commands/{end-game.d.ts → game/end.d.ts} +2 -2
  44. package/dist/commands/{end-game.js → game/end.js} +5 -9
  45. package/dist/commands/{export-game.d.ts → game/export.d.ts} +1 -1
  46. package/dist/commands/{export-game.js → game/export.js} +4 -4
  47. package/dist/commands/game/index.d.ts +11 -0
  48. package/dist/commands/game/index.js +13 -0
  49. package/dist/commands/{levels.d.ts → game/levels.d.ts} +3 -3
  50. package/dist/commands/{levels.js → game/levels.js} +3 -3
  51. package/dist/commands/{look.d.ts → game/look.d.ts} +3 -4
  52. package/dist/commands/{look.js → game/look.js} +5 -10
  53. package/dist/commands/{new-game.d.ts → game/new.d.ts} +3 -4
  54. package/dist/commands/{new-game.js → game/new.js} +17 -13
  55. package/dist/commands/{page-link.d.ts → game/page-link.d.ts} +1 -1
  56. package/dist/commands/{player-move.d.ts → game/player-move.d.ts} +2 -2
  57. package/dist/commands/{player-move.js → game/player-move.js} +4 -8
  58. package/dist/commands/{ponder.d.ts → game/ponder.d.ts} +3 -3
  59. package/dist/commands/{ponder.js → game/ponder.js} +6 -9
  60. package/dist/commands/{position.d.ts → game/position.d.ts} +2 -2
  61. package/dist/commands/{position.js → game/position.js} +5 -7
  62. package/dist/commands/{status.d.ts → game/status.d.ts} +2 -4
  63. package/dist/commands/{status.js → game/status.js} +12 -17
  64. package/dist/commands/index.d.ts +6 -17
  65. package/dist/commands/index.js +11 -38
  66. package/dist/commands/{delete-game.d.ts → profiles/delete-game.d.ts} +1 -1
  67. package/dist/commands/{delete-game.js → profiles/delete-game.js} +3 -3
  68. package/dist/commands/profiles/index.d.ts +4 -0
  69. package/dist/commands/profiles/index.js +5 -0
  70. package/dist/commands/{past-games.d.ts → profiles/past-games.d.ts} +1 -1
  71. package/dist/commands/{past-games.js → profiles/past-games.js} +5 -5
  72. package/dist/commands/{profiles.d.ts → profiles/profiles.d.ts} +7 -7
  73. package/dist/commands/{profiles.js → profiles/profiles.js} +4 -4
  74. package/dist/commands/{ratings.d.ts → profiles/ratings.d.ts} +1 -1
  75. package/dist/commands/{ratings.js → profiles/ratings.js} +2 -2
  76. package/dist/commands/{context.js → registry/context.js} +2 -2
  77. package/dist/commands/registry/index.d.ts +2 -0
  78. package/dist/commands/registry/index.js +3 -0
  79. package/dist/commands/setup/index.d.ts +2 -0
  80. package/dist/commands/setup/index.js +3 -0
  81. package/dist/commands/{init.d.ts → setup/init.d.ts} +1 -1
  82. package/dist/commands/{init.js → setup/init.js} +2 -3
  83. package/dist/commands/{instructions.d.ts → setup/instructions.d.ts} +2 -2
  84. package/dist/commands/{instructions.js → setup/instructions.js} +3 -3
  85. package/dist/commands/{ai-say.d.ts → talk/ai-say.d.ts} +2 -3
  86. package/dist/commands/{ai-say.js → talk/ai-say.js} +5 -9
  87. package/dist/commands/talk/index.d.ts +2 -0
  88. package/dist/commands/talk/index.js +3 -0
  89. package/dist/commands/{player-say.d.ts → talk/player-say.d.ts} +1 -1
  90. package/dist/commands/{player-say.js → talk/player-say.js} +4 -4
  91. package/dist/page/app/app.js +53 -21
  92. package/dist/page/app/served.js +15 -3
  93. package/dist/page/avatars/torretta.js +10 -3
  94. package/dist/page/dialogs/dialogs.css +10 -0
  95. package/dist/page/dialogs/profiles.js +1 -5
  96. package/dist/page/dialogs/settings.js +18 -4
  97. package/dist/page/game/column.js +1 -1
  98. package/dist/page/game/game.css +26 -3
  99. package/dist/page/guide/blocks.js +13 -2
  100. package/dist/page/guide/credits.js +6 -0
  101. package/dist/page/guide/drawings/analysis.js +13 -0
  102. package/dist/page/guide/drawings/drawings.css +329 -0
  103. package/dist/page/guide/drawings/drawings.js +15 -0
  104. package/dist/page/guide/drawings/game.js +15 -0
  105. package/dist/page/guide/drawings/helps.js +13 -0
  106. package/dist/page/guide/drawings/how-it-works.js +13 -0
  107. package/dist/page/guide/drawings/parts.js +103 -0
  108. package/dist/page/guide/drawings/players.js +19 -0
  109. package/dist/page/guide/drawings/talk.js +9 -0
  110. package/dist/page/guide/en.md +39 -25
  111. package/dist/page/guide/guide.css +123 -91
  112. package/dist/page/guide/guide.js +47 -25
  113. package/dist/page/guide/it.md +39 -25
  114. package/dist/page/guide/markdown.js +5 -0
  115. package/dist/page/helps/helps.css +201 -88
  116. package/dist/page/helps/helps.js +50 -31
  117. package/dist/page/helps/view.js +13 -3
  118. package/dist/page/{guide/foot.js → layout/footer.js} +7 -8
  119. package/dist/page/layout/header.js +5 -3
  120. package/dist/page/layout/layout.css +114 -10
  121. package/dist/page/layout/window.js +6 -26
  122. package/dist/page/main.js +1 -1
  123. package/dist/page/pieces/pieces.js +5 -2
  124. package/dist/page/style.css +1 -0
  125. package/dist/page/table/table.css +69 -0
  126. package/dist/page/table/update.js +82 -0
  127. package/dist/page/table/window.js +6 -5
  128. package/dist/page/talk/chat.css +6 -6
  129. package/dist/page/talk/talk.css +17 -67
  130. package/dist/page/talk/talk.js +6 -6
  131. package/dist/page/talk/voice.js +12 -0
  132. package/dist/page/text.js +78 -20
  133. package/dist/page/theme.css +2 -0
  134. package/dist/server/{bell.js → bell/bell.js} +17 -4
  135. package/dist/server/bell/index.d.ts +1 -0
  136. package/dist/server/bell/index.js +2 -0
  137. package/dist/server/{caller.d.ts → mcp/caller.d.ts} +1 -1
  138. package/dist/server/{caller.js → mcp/caller.js} +1 -1
  139. package/dist/server/mcp/index.d.ts +1 -0
  140. package/dist/server/mcp/index.js +2 -0
  141. package/dist/server/{mcp.d.ts → mcp/mcp.d.ts} +1 -1
  142. package/dist/server/{mcp.js → mcp/mcp.js} +9 -9
  143. package/dist/server/page/attach.js +8 -3
  144. package/dist/server/page/game.js +19 -3
  145. package/dist/server/page/http.d.ts +1 -1
  146. package/dist/server/page/index.d.ts +3 -0
  147. package/dist/server/page/index.js +3 -0
  148. package/dist/server/page/page.d.ts +12 -3
  149. package/dist/server/page/page.js +26 -5
  150. package/dist/server/page/table.d.ts +7 -1
  151. package/dist/server/page/table.js +79 -3
  152. package/dist/server/page/update.d.ts +18 -0
  153. package/dist/server/page/update.js +40 -0
  154. package/dist/server/table/browser.d.ts +1 -1
  155. package/dist/server/table/browser.js +1 -1
  156. package/dist/server/table/hosts/chats.d.ts +1 -1
  157. package/dist/server/table/hosts/chats.js +2 -2
  158. package/dist/server/table/hosts/index.d.ts +1 -1
  159. package/dist/server/table/hosts/index.js +1 -1
  160. package/dist/server/table/hosts/play.d.ts +8 -1
  161. package/dist/server/table/hosts/play.js +40 -4
  162. package/dist/server/table/hosts/seat.d.ts +5 -1
  163. package/dist/server/table/hosts/seat.js +1 -1
  164. package/dist/server/table/index.d.ts +3 -2
  165. package/dist/server/table/index.js +3 -2
  166. package/dist/server/table/launch.d.ts +5 -2
  167. package/dist/server/table/launch.js +6 -3
  168. package/dist/server/table/link.d.ts +1 -1
  169. package/dist/server/table/link.js +3 -3
  170. package/dist/server/table/table.d.ts +4 -5
  171. package/dist/server/table/table.js +15 -12
  172. package/dist/server/table/update.d.ts +17 -0
  173. package/dist/server/table/update.js +77 -0
  174. package/dist/sites/index.d.ts +1 -1
  175. package/dist/sites/index.js +2 -2
  176. package/dist/version.d.ts +4 -0
  177. package/dist/version.js +12 -1
  178. package/package.json +1 -1
  179. /package/dist/commands/{speech.d.ts → answers/speech.d.ts} +0 -0
  180. /package/dist/commands/{view.js → answers/view.js} +0 -0
  181. /package/dist/commands/{page-link.js → game/page-link.js} +0 -0
  182. /package/dist/commands/{context.d.ts → registry/context.d.ts} +0 -0
  183. /package/dist/commands/{registry.d.ts → registry/registry.d.ts} +0 -0
  184. /package/dist/commands/{registry.js → registry/registry.js} +0 -0
  185. /package/dist/server/{bell.d.ts → bell/bell.d.ts} +0 -0
@@ -12,7 +12,8 @@ import { askedAgain, leaveSeat, refreshSeat, seatTimes, takeSeat } from "./seat.
12
12
  import { cmdQuoted } from "./terminal.js";
13
13
  /**
14
14
  * Runs the host in the table's folder, in this terminal, until it ends: its exit code; started again, clean, when the table asks for a new game (D-57),
15
- * or for another model or host, which then takes its place (D-107). Ctrl+C is the host's: this process ignores it, or the terminal would close with the chat.
15
+ * or for another model or host, which then takes its place (D-107); after an update, by the Chesspal installed (D-108). Ctrl+C is the host's: this
16
+ * process ignores it, or the terminal would close with the chat.
16
17
  */
17
18
  export async function playHost(context, host) {
18
19
  const folder = tableFolder(context.home());
@@ -29,6 +30,8 @@ export async function playHost(context, host) {
29
30
  // The host before one asked in its place, until that one has started: it goes on when the other does not start.
30
31
  let before;
31
32
  let refreshed = Date.now();
33
+ // Chesspal was updated under this process (D-108): the new one plays on.
34
+ let updated = false;
32
35
  const asked = () => {
33
36
  if (Date.now() - refreshed >= seatTimes.refresh) {
34
37
  refreshed = Date.now();
@@ -36,6 +39,7 @@ export async function playHost(context, host) {
36
39
  }
37
40
  const again = askedAgain(folder, process.pid);
38
41
  next = again?.host !== undefined && isHostName(again.host) ? again.host : playing;
42
+ updated ||= again?.updated === true;
39
43
  return again !== undefined;
40
44
  };
41
45
  try {
@@ -58,6 +62,12 @@ export async function playHost(context, host) {
58
62
  if (!ran.again) {
59
63
  return ran.code;
60
64
  }
65
+ if (updated) {
66
+ terminal.restore();
67
+ process.stdout.write(cleanScreen);
68
+ leaveSeat(folder, process.pid);
69
+ return await playUpdated(playing);
70
+ }
61
71
  if (next !== playing) {
62
72
  before = playing;
63
73
  playing = next;
@@ -131,14 +141,40 @@ async function started(context, host, again) {
131
141
  throw new Error(`${hostTitle[host]} did not start: ${error.message}`);
132
142
  });
133
143
  }
144
+ // The Chesspal just installed plays on in this terminal, in a process of its own with all its files new (D-108), which takes the seat; this one, with
145
+ // the code it started with, waits for it to end.
146
+ function playUpdated(host) {
147
+ return new Promise((done) => {
148
+ const startedAt = Date.now();
149
+ const play = spawn(process.execPath, [...process.execArgv, process.argv[1] ?? "", "play", host], { stdio: "inherit" });
150
+ const failed = (why) => {
151
+ process.stderr.write(`Chesspal did not start again after its update (${why}): run chesspal play ${host}.\n`);
152
+ };
153
+ play.once("error", (error) => {
154
+ failed(error.message);
155
+ done(1);
156
+ });
157
+ // One that ended at once, failing, never showed its host: the player is told how to start it (Priya).
158
+ play.once("exit", (code) => {
159
+ if (code !== 0 && Date.now() - startedAt < quickExit) {
160
+ failed(`exit code ${code ?? "none"}`);
161
+ }
162
+ done(code ?? 1);
163
+ });
164
+ });
165
+ }
134
166
  // A host that has not ended this long after it was asked is killed.
135
167
  const killAfter = 3_000;
168
+ // A play process that ends sooner never got to its host.
169
+ const quickExit = 10_000;
136
170
  // A host ended in the middle leaves the modes it set: colours, scroll region, synchronized output, bracketed paste, focus and mouse reports, kitty's
137
171
  // keyboard flags, its own screen, a hidden cursor (Priya). All off, and the screen cleared, as a new chat.
138
172
  const cleanScreen = "\x1b[0m\x1b[r\x1b[?2026l\x1b[?2004l\x1b[?1004l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[<99u\x1b[?1049l\x1b[?25h\x1b[2J\x1b[3J\x1b[H";
139
- // The host and what it started, its MCP server among them: on Windows its whole tree, which taskkill ends, never the console (Priya); else the host,
140
- // which ends its own. A taskkill that failed leaves it to the kill that follows.
141
- function endTree(chat, platform) {
173
+ /**
174
+ * Ends a process and what it started, a host's MCP server among them: on Windows its whole tree, which taskkill ends, never the console (Priya); else the
175
+ * process, which ends its own. A taskkill that failed leaves it to a kill.
176
+ */
177
+ export function endTree(chat, platform) {
142
178
  if (platform !== "win32" || chat.pid === undefined) {
143
179
  chat.kill("SIGTERM");
144
180
  return;
@@ -20,9 +20,13 @@ export declare function seatTaken(folder: string, now?: Date): boolean;
20
20
  export declare function seatHeld(folder: string, now?: Date): Seat | undefined;
21
21
  /** Asks the play process in the seat to start its host again, or `again.host` in its place: whether one holds it, and was asked. */
22
22
  export declare function askAgain(folder: string, now?: Date, again?: Again): boolean;
23
- /** What the table asks the play process in the seat: to start its host again, or the host named in its place (D-107). */
23
+ /**
24
+ * What the table asks the play process in the seat: to start its host again, or the host named in its place (D-107); `updated`, from the Chesspal just
25
+ * installed, whose `chesspal play` then takes the seat (D-108).
26
+ */
24
27
  export interface Again {
25
28
  readonly host?: string;
29
+ readonly updated?: true;
26
30
  }
27
31
  /** What the table asked this play process, when it asked it to start its host again; the request is taken. */
28
32
  export declare function askedAgain(folder: string, pid: number): Again | undefined;
@@ -56,7 +56,7 @@ export function askedAgain(folder, pid) {
56
56
  return undefined;
57
57
  }
58
58
  rmSync(join(folder, againFile), { force: true });
59
- return typeof asked.host === "string" ? { host: asked.host } : {};
59
+ return { ...(typeof asked.host === "string" ? { host: asked.host } : {}), ...(asked.updated === true ? { updated: true } : {}) };
60
60
  }
61
61
  /** How often a play process refreshes its seat, and how long the seat stays held without it. */
62
62
  export const seatTimes = { refresh: 5_000, heldFor: 15_000 };
@@ -1,6 +1,7 @@
1
1
  export { appBrowser, openedAddress } from "./browser.ts";
2
2
  export { hostLaunch, isHostName, playHost, terminalChats } from "./hosts/index.ts";
3
3
  export { iconAsked, type IconCommand, iconCommand, type IconPlaces, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "./icon/index.ts";
4
- export { noteOpenFailed, openTable, processLauncher, type TableLauncher } from "./launch.ts";
4
+ export { noteFailure, openTable, processLauncher, type TableLauncher } from "./launch.ts";
5
5
  export { attachTable, tableToJoin } from "./link.ts";
6
- export { isOlder, startTable, type Table, type TableTimes, tableTimes } from "./table.ts";
6
+ export { startTable, type Table, type TableTimes, tableTimes } from "./table.ts";
7
+ export { npmInstall, npmUpdater, type TableUpdater } from "./update.ts";
@@ -2,6 +2,7 @@
2
2
  export { appBrowser, openedAddress } from "./browser.js";
3
3
  export { hostLaunch, isHostName, playHost, terminalChats } from "./hosts/index.js";
4
4
  export { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "./icon/index.js";
5
- export { noteOpenFailed, openTable, processLauncher } from "./launch.js";
5
+ export { noteFailure, openTable, processLauncher } from "./launch.js";
6
6
  export { attachTable, tableToJoin } from "./link.js";
7
- export { isOlder, startTable, tableTimes } from "./table.js";
7
+ export { startTable, tableTimes } from "./table.js";
8
+ export { npmInstall, npmUpdater } from "./update.js";
@@ -13,7 +13,10 @@ export interface TableLauncher {
13
13
  * within ten seconds throws.
14
14
  */
15
15
  export declare function openTable(context: Context, launcher?: TableLauncher): Promise<string>;
16
- /** Notes why the icon could not open the table, whose console nobody sees, in open.log of the table's folder: the last of them. */
17
- export declare function noteOpenFailed(context: Context, message: string): void;
16
+ /**
17
+ * Notes what went wrong where nobody reads a console, in a log of the table's folder, the last time alone: open.log, why the icon could not open the table;
18
+ * update.log, why npm did not update Chesspal (D-108).
19
+ */
20
+ export declare function noteFailure(context: Context, log: "open.log" | "update.log", message: string): void;
18
21
  /** The launcher of a real `chesspal`: the table started with the same Node and script, the page opened in the default browser. */
19
22
  export declare function processLauncher(platform: string, command?: string[]): TableLauncher;
@@ -39,12 +39,15 @@ export async function openTable(context, launcher = processLauncher(context.plat
39
39
  await launcher.wait(every);
40
40
  }
41
41
  }
42
- /** Notes why the icon could not open the table, whose console nobody sees, in open.log of the table's folder: the last of them. */
43
- export function noteOpenFailed(context, message) {
42
+ /**
43
+ * Notes what went wrong where nobody reads a console, in a log of the table's folder, the last time alone: open.log, why the icon could not open the table;
44
+ * update.log, why npm did not update Chesspal (D-108).
45
+ */
46
+ export function noteFailure(context, log, message) {
44
47
  try {
45
48
  const folder = tableFolder(context.home());
46
49
  mkdirSync(folder, { recursive: true });
47
- writeFileSync(join(folder, "open.log"), `${context.now().toISOString()} ${message}\n`);
50
+ writeFileSync(join(folder, log), `${context.now().toISOString()} ${message}\n`);
48
51
  }
49
52
  catch {
50
53
  // A data folder that cannot be written has no other place for it.
@@ -1,5 +1,5 @@
1
1
  import type { Context } from "../../commands/index.ts";
2
- import type { Page } from "../page/page.ts";
2
+ import { type Page } from "../page/index.ts";
3
3
  /**
4
4
  * Attaches the chat to the table of its data folder, when one of its version answers and no other chat is at it: the page the chat then uses, whose `lost`
5
5
  * is told once the table is gone or no longer knows the chat. An older table is asked to quit; with none, a newer one or another chat at it, none, and
@@ -5,9 +5,9 @@ import { aiHostOf, aiModelOf } from "../../games/index.js";
5
5
  import { tableFolder } from "../../home/index.js";
6
6
  import { outdatedHostFiles } from "../../host-files/index.js";
7
7
  import { gameFolder } from "../../profiles/index.js";
8
- import { version } from "../../version.js";
9
- import { jsonOf } from "../page/http.js";
10
- import { askToQuit, isOlder, tableAt } from "./table.js";
8
+ import { isOlder, version } from "../../version.js";
9
+ import { jsonOf } from "../page/index.js";
10
+ import { askToQuit, tableAt } from "./table.js";
11
11
  /**
12
12
  * Attaches the chat to the table of its data folder, when one of its version answers and no other chat is at it: the page the chat then uses, whose `lost`
13
13
  * is told once the table is gone or no longer knows the chat. An older table is asked to quit; with none, a newer one or another chat at it, none, and
@@ -1,5 +1,6 @@
1
1
  import type { Context } from "../../commands/index.ts";
2
- import type { TableChats } from "../page/table.ts";
2
+ import { type TableChats } from "../page/index.ts";
3
+ import type { TableUpdater } from "./update.ts";
3
4
  /** A table served: its address, and how to close it. */
4
5
  export interface Table {
5
6
  readonly url: string;
@@ -24,12 +25,10 @@ export interface TableFound {
24
25
  /**
25
26
  * Starts the table on the context's data folder, its folder set up as init does, its page opening chats through `chats`: undefined when another table
26
27
  * started at the same time took a lower port, since a table's ports are its lock. It closes once no page has been open, no chat attached and no request
27
- * come, for `times.idle`.
28
+ * come, for `times.idle`; with `updater`, its page updates Chesspal, and a new table starts from it once this one has closed (D-108).
28
29
  */
29
- export declare function startTable(context: Context, times?: TableTimes, chats?: TableChats): Promise<Table | undefined>;
30
+ export declare function startTable(context: Context, times?: TableTimes, chats?: TableChats, updater?: TableUpdater): Promise<Table | undefined>;
30
31
  /** The table of a folder that answers on its ports, the lowest of them; its ports are asked together. */
31
32
  export declare function tableAt(folder: string): Promise<TableFound | undefined>;
32
33
  /** Asks the table at a port to quit; it answers before it goes, and a table that cannot be reached, or does not answer within a second or so, is let be. */
33
34
  export declare function askToQuit(port: number): Promise<void>;
34
- /** Whether a version is older than another, part by part: 0.2.4 is older than 0.10.0. */
35
- export declare function isOlder(one: string, other: string): boolean;
@@ -3,16 +3,16 @@
3
3
  import { request } from "node:http";
4
4
  import { tableFolder } from "../../home/index.js";
5
5
  import { setUpHosts } from "../../host-files/index.js";
6
- import { pageAt, pagePorts, samePath } from "../bell.js";
7
- import { startPage } from "../page/page.js";
6
+ import { pageAt, pagePorts, samePath } from "../bell/index.js";
7
+ import { startPage } from "../page/index.js";
8
8
  /** Five minutes with no browser and no chat, looked at every fifteen seconds (D-104). */
9
9
  export const tableTimes = { idle: 5 * 60_000, every: 15_000 };
10
10
  /**
11
11
  * Starts the table on the context's data folder, its folder set up as init does, its page opening chats through `chats`: undefined when another table
12
12
  * started at the same time took a lower port, since a table's ports are its lock. It closes once no page has been open, no chat attached and no request
13
- * come, for `times.idle`.
13
+ * come, for `times.idle`; with `updater`, its page updates Chesspal, and a new table starts from it once this one has closed (D-108).
14
14
  */
15
- export async function startTable(context, times = tableTimes, chats) {
15
+ export async function startTable(context, times = tableTimes, chats, updater) {
16
16
  const folder = tableFolder(context.home());
17
17
  setUpHosts(folder, context.platform);
18
18
  let quit = () => undefined;
@@ -20,10 +20,16 @@ export async function startTable(context, times = tableTimes, chats) {
20
20
  quit = done;
21
21
  });
22
22
  let lastUsed = context.now().getTime();
23
+ let startAgain = false;
23
24
  const used = () => {
24
25
  lastUsed = context.now().getTime();
25
26
  };
26
- const page = await startPage({ ...context, folder }, { table: { used, quit, chats } });
27
+ const restart = () => {
28
+ startAgain = true;
29
+ quit();
30
+ };
31
+ const update = updater === undefined ? undefined : { install: (version) => updater.install(version), restart };
32
+ const page = await startPage({ ...context, folder }, { table: { used, quit, chats, update } });
27
33
  if (await lostTo(page.url, folder)) {
28
34
  await page.close();
29
35
  return undefined;
@@ -31,7 +37,11 @@ export async function startTable(context, times = tableTimes, chats) {
31
37
  const watch = closeWhenIdle(page, () => context.now().getTime() - lastUsed, used, times, quit);
32
38
  const closed = quitting.then(async () => {
33
39
  clearInterval(watch);
40
+ // Its ports free, the new table takes the first of them, where the pages open look for it.
34
41
  await page.close();
42
+ if (startAgain) {
43
+ updater?.startAgain();
44
+ }
35
45
  });
36
46
  return {
37
47
  url: page.url,
@@ -64,13 +74,6 @@ export function askToQuit(port) {
64
74
  }
65
75
  // A table answers at once: one stuck must not hold the chat's first tool, nor `chesspal`.
66
76
  const quitWait = 1_500;
67
- /** Whether a version is older than another, part by part: 0.2.4 is older than 0.10.0. */
68
- export function isOlder(one, other) {
69
- const parts = (version) => version.split(/[.+-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
70
- const [mine, theirs] = [parts(one), parts(other)];
71
- const differs = mine.findIndex((part, index) => part !== theirs[index]);
72
- return differs !== -1 && mine[differs] < theirs[differs];
73
- }
74
77
  /** Whether a table of the same folder answers on a lower port than this one's: the other won, and this one goes. */
75
78
  async function lostTo(url, folder) {
76
79
  const port = Number(new URL(url).port);
@@ -0,0 +1,17 @@
1
+ import type { Context } from "../../commands/index.ts";
2
+ import { type TableLauncher } from "./launch.ts";
3
+ /** How a table updates Chesspal: npm installs a version, whether it did, and once the table has closed a new one starts from it. */
4
+ export interface TableUpdater {
5
+ install(version: string): Promise<boolean>;
6
+ startAgain(): void;
7
+ }
8
+ /**
9
+ * A real table's updater: npm on the PATH, and `chesspal table` started apart, as `chesspal` starts it. A Chesspal npm did not install, run from its
10
+ * source or linked to it, npm leaves alone: it would put another in its place for the whole computer.
11
+ */
12
+ export declare function npmUpdater(context: Context, launcher?: TableLauncher): TableUpdater;
13
+ /**
14
+ * Installs a version of Chesspal for the whole computer with the npm on the PATH: whether it is now the one on disk where this one runs from, which an npm
15
+ * of another Node does not reach. What npm said when it failed goes in update.log of the table's folder.
16
+ */
17
+ export declare function npmInstall(context: Context, onDisk?: () => string, env?: NodeJS.ProcessEnv): (version: string) => Promise<boolean>;
@@ -0,0 +1,77 @@
1
+ // The table updates Chesspal (D-108): the npm on the PATH installs the version, as the player would type it, and a new table starts from it once this one
2
+ // has closed; what npm said when it failed goes in update.log of the table's folder.
3
+ import { spawn } from "node:child_process";
4
+ import { basename, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { installedVersion } from "../../version.js";
7
+ import { endTree } from "./hosts/index.js";
8
+ import { noteFailure, processLauncher } from "./launch.js";
9
+ /**
10
+ * A real table's updater: npm on the PATH, and `chesspal table` started apart, as `chesspal` starts it. A Chesspal npm did not install, run from its
11
+ * source or linked to it, npm leaves alone: it would put another in its place for the whole computer.
12
+ */
13
+ export function npmUpdater(context, launcher = processLauncher(context.platform)) {
14
+ const root = fileURLToPath(new URL("../../../", import.meta.url));
15
+ const fromNpm = basename(root) === "chesspal" && basename(dirname(root)) === "node_modules";
16
+ const install = fromNpm ? npmInstall(context) : () => {
17
+ noteFailure(context, "update.log", `Chesspal runs from ${root}, which npm did not install: update it the way it was installed.`);
18
+ return Promise.resolve(false);
19
+ };
20
+ return { install, startAgain: () => launcher.start(context.home()) };
21
+ }
22
+ /**
23
+ * Installs a version of Chesspal for the whole computer with the npm on the PATH: whether it is now the one on disk where this one runs from, which an npm
24
+ * of another Node does not reach. What npm said when it failed goes in update.log of the table's folder.
25
+ */
26
+ export function npmInstall(context, onDisk = installedVersion, env = process.env) {
27
+ const { platform } = context;
28
+ return (version) => new Promise((done) => {
29
+ // A plain release alone: on Windows the version goes through cmd.
30
+ if (!/^\d+\.\d+\.\d+$/.test(version)) {
31
+ done(false);
32
+ return;
33
+ }
34
+ const words = ["install", "--global", `chesspal@${version}`];
35
+ // cmd would look for npm in the table's folder first, where anything may lie (Federico).
36
+ const options = { stdio: ["ignore", "ignore", "pipe"], windowsHide: true, env: { ...env, NoDefaultCurrentDirectoryInExePath: "1" } };
37
+ // npm is a .cmd shim on Windows, which only cmd runs.
38
+ const npm = platform === "win32" ? spawn(`npm ${words.join(" ")}`, { ...options, shell: true }) : spawn("npm", words, options);
39
+ let said = "";
40
+ let settled = false;
41
+ const end = (installed, why) => {
42
+ if (settled) {
43
+ return;
44
+ }
45
+ settled = true;
46
+ clearTimeout(timer);
47
+ if (!installed) {
48
+ noteFailure(context, "update.log", `npm ${words.join(" ")}: ${why}\n${said}`);
49
+ }
50
+ done(installed);
51
+ };
52
+ // On Windows killing cmd leaves npm running under it: its whole tree goes (Federico).
53
+ const timer = setTimeout(() => {
54
+ endTree(npm, platform);
55
+ end(false, `no answer in ${installWait / 60_000} minutes`);
56
+ }, installWait);
57
+ npm.stderr.on("data", (chunk) => {
58
+ said = (said + chunk.toString()).slice(-logLength);
59
+ });
60
+ npm.once("error", (error) => end(false, error.message));
61
+ npm.once("close", (code) => {
62
+ const now = code === 0 ? versionOnDisk(onDisk) : undefined;
63
+ end(now === version, code === 0 ? `Chesspal ${now ?? "unreadable"} is on disk where this table runs from` : `exit code ${code ?? "none"}`);
64
+ });
65
+ });
66
+ }
67
+ // npm downloads and installs in a few seconds, or a minute on a slow line; one that takes longer is stuck.
68
+ const installWait = 5 * 60_000;
69
+ const logLength = 4000;
70
+ function versionOnDisk(onDisk) {
71
+ try {
72
+ return onDisk();
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ }
@@ -1,2 +1,2 @@
1
- export { type Fetch } from "./request.ts";
1
+ export { answerTime, askSite, type Fetch } from "./request.ts";
2
2
  export { readingsStale, readSites } from "./sites.ts";
@@ -1,4 +1,4 @@
1
1
  // The sites the player plays on (D-24, D-105): their public ratings on Lichess and chess.com, read from the names in their profile, without a key. Below
2
- // the commands, beside the profiles, whose types it fills; never asked by a tool of the AI's (D-106).
3
- export {} from "./request.js";
2
+ // the commands, beside the profiles, whose types it fills; never asked by a tool of the AI's (D-106). Its request asks npm too, for a newer Chesspal (D-108).
3
+ export { answerTime, askSite } from "./request.js";
4
4
  export { readingsStale, readSites } from "./sites.js";
package/dist/version.d.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  /** The version of the package that runs, read from its package.json: one folder above both src/ and dist/, so this module stays at their root. */
2
2
  export declare const version: string;
3
+ /** The version of the package on disk now, which an update installs under the one that runs (D-108). */
4
+ export declare function installedVersion(): string;
5
+ /** Whether a version is older than another, part by part: 0.2.4 is older than 0.10.0. */
6
+ export declare function isOlder(one: string, other: string): boolean;
package/dist/version.js CHANGED
@@ -1,3 +1,14 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  /** The version of the package that runs, read from its package.json: one folder above both src/ and dist/, so this module stays at their root. */
3
- export const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
3
+ export const version = installedVersion();
4
+ /** The version of the package on disk now, which an update installs under the one that runs (D-108). */
5
+ export function installedVersion() {
6
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
7
+ }
8
+ /** Whether a version is older than another, part by part: 0.2.4 is older than 0.10.0. */
9
+ export function isOlder(one, other) {
10
+ const parts = (version) => version.split(/[.+-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
11
+ const [mine, theirs] = [parts(one), parts(other)];
12
+ const differs = mine.findIndex((part, index) => part !== theirs[index]);
13
+ return differs !== -1 && mine[differs] < theirs[differs];
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chesspal",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Play chess against an AI with a character, and learn with an AI coach: Chesspal keeps the rules, the board, the games and the memory; the AI plays, reasons and talks.",
5
5
  "type": "module",
6
6
  "bin": {
File without changes
File without changes