chesspal 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +39 -13
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +46 -13
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +40 -18
- package/dist/commands/answer-lines.d.ts +2 -0
- package/dist/commands/answer-lines.js +13 -3
- package/dist/commands/coach.js +4 -2
- package/dist/commands/context.d.ts +9 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -5
- package/dist/commands/game-summary.d.ts +2 -0
- package/dist/commands/game-summary.js +4 -0
- package/dist/commands/index.d.ts +3 -2
- package/dist/commands/index.js +7 -4
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +46 -27
- package/dist/commands/study.js +8 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/review.d.ts +2 -0
- package/dist/core/review.js +5 -1
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +26 -3
- package/dist/games/analysis.js +41 -6
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +10 -1
- package/dist/games/current-game.js +49 -15
- package/dist/games/files.d.ts +5 -0
- package/dist/games/files.js +42 -1
- package/dist/games/index.d.ts +3 -3
- package/dist/games/index.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +29 -16
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +95 -42
- package/dist/page/game/column.js +5 -1
- package/dist/page/game/plies.js +7 -4
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +7 -10
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +111 -4
- package/dist/page/layout/window.js +45 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +2 -0
- package/dist/page/table/opener.css +65 -0
- package/dist/page/table/opener.js +59 -0
- package/dist/page/talk/chat.css +10 -3
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +4 -2
- package/dist/page/text.js +83 -17
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +100 -52
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +60 -0
- package/dist/server/page/chat.js +79 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +43 -23
- package/dist/server/page/page.d.ts +47 -7
- package/dist/server/page/page.js +69 -48
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +39 -0
- package/dist/server/page/table.js +102 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +31 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +25 -0
- package/dist/server/table/hosts/play.js +152 -0
- package/dist/server/table/hosts/seat.d.ts +25 -0
- package/dist/server/table/hosts/seat.js +74 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +230 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
package/dist/commands/status.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { gameReviewOf, leadOf, reviewOf } from "../core/index.js";
|
|
3
3
|
import { archivedName, cleanSince, endOf, gameInAnalysis, lastChatStart } from "../games/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { outdatedHostFiles } from "../host-files/index.js";
|
|
5
5
|
import { gameFolder } from "../profiles/index.js";
|
|
6
6
|
import { version } from "../version.js";
|
|
7
|
-
import {
|
|
8
|
-
import { closingLines, newGameStep, positionLines, sidesLine, wordLines } from "./answer-lines.js";
|
|
7
|
+
import { analystLine, analystRuns, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
|
|
8
|
+
import { closingLines, newGameStep, positionLines, shownMoves, sidesLine, wordLines } from "./answer-lines.js";
|
|
9
9
|
import { gameAt } from "./clock.js";
|
|
10
10
|
import { summaryOf, withThought } from "./game-summary.js";
|
|
11
|
+
import { instructionsRead } from "./instructions.js";
|
|
11
12
|
import { levelText } from "./levels.js";
|
|
12
13
|
import { closedPage, pageGiven, pageLine } from "./page-link.js";
|
|
13
14
|
import { playerLine } from "./profiles.js";
|
|
@@ -19,7 +20,7 @@ import { namedGame } from "./study.js";
|
|
|
19
20
|
export const status = defineCommand({
|
|
20
21
|
name: "status",
|
|
21
22
|
description: "Shows what Chesspal sees now: its version, the profile's folder and the game, with your next step. Call it at the start of every chat, "
|
|
22
|
-
+ "
|
|
23
|
+
+ "with `instructions`.",
|
|
23
24
|
input: z.object({}),
|
|
24
25
|
// Read only for the host: a flag that has fallen is written, as the time already decided.
|
|
25
26
|
readOnly: true,
|
|
@@ -30,7 +31,10 @@ export const status = defineCommand({
|
|
|
30
31
|
const folder = gameFolder(context.home());
|
|
31
32
|
const player = playerLine(context);
|
|
32
33
|
const speech = speechOf(folder, context.language);
|
|
33
|
-
const
|
|
34
|
+
const chatStart = lastChatStart(context.folder);
|
|
35
|
+
const first = firstInChat(context, chatStart?.at);
|
|
36
|
+
// An older init's files are told once a chat, at its first status: reading them all is the slowest part of a status.
|
|
37
|
+
const outdated = context.told === undefined || first ? outdatedHostFiles(context.folder, context.platform) : [];
|
|
34
38
|
const status = { version, folder, ...(player === undefined ? {} : { player }), ...(speech === undefined ? {} : { speech }), ...(outdated.length === 0 ? {} : { outdated }) };
|
|
35
39
|
if (context.page !== undefined) {
|
|
36
40
|
status.page = context.page;
|
|
@@ -38,7 +42,9 @@ export const status = defineCommand({
|
|
|
38
42
|
if (closedPage(context) !== undefined) {
|
|
39
43
|
status.pageClosed = true;
|
|
40
44
|
}
|
|
41
|
-
|
|
45
|
+
if (!instructionsRead(context, chatStart?.at)) {
|
|
46
|
+
status.unread = true;
|
|
47
|
+
}
|
|
42
48
|
try {
|
|
43
49
|
const current = gameAt(folder, context.now());
|
|
44
50
|
// With no game the player may have written before the first (D-77).
|
|
@@ -51,7 +57,8 @@ export const status = defineCommand({
|
|
|
51
57
|
if (status.pageClosed === true) {
|
|
52
58
|
pageGiven(context, current.started);
|
|
53
59
|
}
|
|
54
|
-
|
|
60
|
+
// A game a summarized or resumed chat still holds is its own; without the host's word of the chat's start, any game began before it.
|
|
61
|
+
if (first && endOf(current.game) === undefined && (chatStart === undefined || cleanSince(chatStart, current.started))) {
|
|
55
62
|
status.fromBefore = true;
|
|
56
63
|
}
|
|
57
64
|
// The player's move on the board page that ended the game: the coach judges it too (D-88).
|
|
@@ -61,12 +68,12 @@ export const status = defineCommand({
|
|
|
61
68
|
}
|
|
62
69
|
const analysis = current === undefined || status.game?.end !== undefined ? gameInAnalysis(folder) : undefined;
|
|
63
70
|
if (analysis !== undefined) {
|
|
64
|
-
// The analyst agent goes over a game the player brought, where it runs, and the chat over what it reviewed (D-89).
|
|
71
|
+
// The analyst agent goes over a game the player brought, or whose review was deleted, where it runs, and the chat over what it reviewed (D-89).
|
|
65
72
|
const review = analysis.brought === true ? undefined : reviewProgress(context, analysis.game);
|
|
66
73
|
status.analysis = {
|
|
67
74
|
game: analysis.game,
|
|
68
75
|
...(analysis.brought === true ? { brought: true } : {}),
|
|
69
|
-
...(analystRuns(context) ? { analyst:
|
|
76
|
+
...(analystRuns(context) ? { analyst: goneOver(context, analysis.game) ? "done" : "offer" } : {}),
|
|
70
77
|
...(review === undefined ? {} : { review }),
|
|
71
78
|
};
|
|
72
79
|
}
|
|
@@ -76,8 +83,9 @@ export const status = defineCommand({
|
|
|
76
83
|
}
|
|
77
84
|
return status;
|
|
78
85
|
},
|
|
79
|
-
format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated }) => [
|
|
86
|
+
format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated, unread }) => [
|
|
80
87
|
`Chesspal ${version}. Folder: ${folder}`,
|
|
88
|
+
...(unread === true ? [unreadLine] : []),
|
|
81
89
|
...(outdated === undefined ? [] : [outdatedLine(outdated)]),
|
|
82
90
|
...(page === undefined ? [] : [pageLine(page, pageClosed === true)]),
|
|
83
91
|
...(player === undefined ? [] : [player]),
|
|
@@ -92,48 +100,58 @@ function outdatedLine(files) {
|
|
|
92
100
|
return `This folder was set up by an older Chesspal (${files.join(", ")}): tell the player, in their language, to close this chat, run "chesspal init" `
|
|
93
101
|
+ "in this folder and open a new chat. Until then the agents and your instructions are the old ones.";
|
|
94
102
|
}
|
|
103
|
+
// The opponent's instructions, which the chat reads before its first reply (D-102).
|
|
104
|
+
const unreadLine = "Call `instructions` before you answer the player, if you have not called it since this chat started or was summarized.";
|
|
95
105
|
// The game going on as a chat starts: the player may have come back for it, or want another.
|
|
96
106
|
const fromBeforeLine = "A game from before this chat is going on: ask the player whether to go on with it. For a new one, " + newGameStep;
|
|
97
107
|
/**
|
|
98
108
|
* Whether this is the chat's first status: once a chat where the MCP server keeps what it told, again in a chat cleared or summarized, since its start keys
|
|
99
109
|
* it (D-57); never on the command line.
|
|
100
110
|
*/
|
|
101
|
-
function firstInChat(context) {
|
|
102
|
-
const key = `game before ${
|
|
111
|
+
function firstInChat(context, chatStart) {
|
|
112
|
+
const key = `game before ${chatStart ?? ""}`;
|
|
103
113
|
if (context.told === undefined || context.told.has(key)) {
|
|
104
114
|
return false;
|
|
105
115
|
}
|
|
106
116
|
context.told.add(key);
|
|
107
117
|
return true;
|
|
108
118
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
/** 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. */
|
|
119
|
+
/**
|
|
120
|
+
* 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
|
|
121
|
+
* already; the analyst agent for a game whose review was deleted, where it runs (Stefano).
|
|
122
|
+
*/
|
|
115
123
|
function analysisLine({ game, brought, analyst, review }) {
|
|
116
|
-
if (brought === true) {
|
|
117
|
-
return analyst ===
|
|
124
|
+
if (brought === true && analyst !== "done") {
|
|
125
|
+
return analyst === "offer"
|
|
118
126
|
? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game)}`
|
|
119
127
|
: `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.`;
|
|
120
128
|
}
|
|
121
129
|
const shown = `In analysis on the board page: ${game}${review === undefined ? "" : ` (${review})`}`;
|
|
122
|
-
|
|
130
|
+
if (analyst === "done") {
|
|
131
|
+
return `${shown}. ${reviewedLine(game)}`;
|
|
132
|
+
}
|
|
133
|
+
return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
|
|
123
134
|
}
|
|
124
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* 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
|
|
137
|
+
* but the last; the analyst may still write notes after its word, so that alone is not the end.
|
|
138
|
+
*/
|
|
125
139
|
function reviewProgress(context, game) {
|
|
126
140
|
try {
|
|
127
141
|
const { record } = namedGame(context, game);
|
|
128
|
-
|
|
142
|
+
const worded = gameReviewOf(record) !== undefined;
|
|
143
|
+
const estimated = record.moves.filter(({ comment }) => leadOf(comment) !== undefined).length;
|
|
144
|
+
if (worded && estimated >= record.moves.length - 1) {
|
|
129
145
|
return "its review is done";
|
|
130
146
|
}
|
|
131
147
|
const noted = record.moves.filter(({ comment }) => {
|
|
132
148
|
const review = reviewOf(comment);
|
|
133
149
|
return review !== undefined && (review.badge !== "book" || review.note !== undefined);
|
|
134
150
|
}).length;
|
|
135
|
-
const
|
|
136
|
-
return noted === 0 && estimated === 0
|
|
151
|
+
const word = worded ? ", its word on the game written" : "";
|
|
152
|
+
return noted === 0 && estimated === 0 && !worded
|
|
153
|
+
? "no review yet"
|
|
154
|
+
: `its review so far: notes on ${noted} moves, who is ahead after ${estimated} of ${record.moves.length}${word}`;
|
|
137
155
|
}
|
|
138
156
|
catch {
|
|
139
157
|
return undefined;
|
|
@@ -144,10 +162,11 @@ function reviewProgress(context, game) {
|
|
|
144
162
|
* the AI acts on it (Priya).
|
|
145
163
|
*/
|
|
146
164
|
function gameLines(game, words, brought, pageClosed, coach) {
|
|
147
|
-
const closing = [...(coach ? [
|
|
165
|
+
const closing = [...(coach ? [coachOnEndLine] : []), ...(brought ? wordLines(words) : closingLines(game, words, pageClosed))];
|
|
148
166
|
if (game === undefined) {
|
|
149
167
|
return ["No game.", ...closing];
|
|
150
168
|
}
|
|
151
|
-
|
|
169
|
+
// With the figurines, as every answer writes moves: in letters the AI copied them into its message (Priya).
|
|
170
|
+
const moves = game.lastMoves === "" ? [] : [`Moves: ${shownMoves(game.lastMoves)}`];
|
|
152
171
|
return [`Game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`, ...moves, ...positionLines(game), ...closing];
|
|
153
172
|
}
|
package/dist/commands/study.js
CHANGED
|
@@ -27,16 +27,19 @@ export function gameToStudy(context, name) {
|
|
|
27
27
|
if (name !== undefined) {
|
|
28
28
|
return namedGame(context, name);
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
// A game closed before any move has nothing to study: the one before it is the last played.
|
|
31
|
+
if (current !== undefined && current.game.record.moves.length > 0) {
|
|
31
32
|
// The archive's record, which keeps the AI's review (D-69).
|
|
32
33
|
const archived = archivedName(folder, current);
|
|
33
34
|
return archived === undefined ? { record: current.game.record } : { record: archivedGame(folder, archived), name: archived };
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
for (const latest of archivedGames(folder)) {
|
|
37
|
+
const record = archivedGame(folder, latest);
|
|
38
|
+
if (record.moves.length > 0) {
|
|
39
|
+
return { record, name: latest };
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
|
-
|
|
42
|
+
throw new Error("There is no game to study: play one with new_game, or give analyse_game the PGN of a game played elsewhere.");
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
42
45
|
* The game look and position show: without a name, for the chat, the one in analysis on the board page, which the player's words there name by their move
|
package/dist/core/index.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export type { Capture, Chances, DoubleAttack, Possibilities, StalemateTrap } fro
|
|
|
18
18
|
export type { BishopFacts, CentreSquare, KingFacts, Outpost, PawnFacts, PawnSquare, Phase, Profile, SideProfile } from "./profile.ts";
|
|
19
19
|
export { movesOnly } from "./record.ts";
|
|
20
20
|
export type { Annotation, GameRecord, RecordedMove } from "./record.ts";
|
|
21
|
-
export { aiHostIn, type AiModel, aiModelIn, aiNameOf, annotatorOf, arrowOf, type Badge, badges, closingOf, type DrawColour, drawingsIn, drawingsText, type DrawnArrow, type DrawnSquare, gameReviewOf, isReviewed, type MoveReview, reviewed, reviewOf, squareOf, unreviewed, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.ts";
|
|
21
|
+
export { aiHostIn, type AiModel, aiModelIn, aiNameOf, annotatorOf, arrowOf, type Badge, badges, closingOf, type DrawColour, drawingsIn, drawingsText, type DrawnArrow, type DrawnSquare, gameReviewOf, isReviewed, type MoveReview, reviewed, reviewOf, squareOf, unreviewed, unsigned, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.ts";
|
package/dist/core/index.js
CHANGED
|
@@ -9,4 +9,4 @@ export { endWithMood, moodAt, moodOf, moods, moodTag, withoutMoods } from "./moo
|
|
|
9
9
|
export { bookMoves, indexBook, indexOpenings, inOpenings, openingOf, outOfBookMove } from "./openings.js";
|
|
10
10
|
export { pgnDate, readPgn, writePgn } from "./pgn.js";
|
|
11
11
|
export { movesOnly } from "./record.js";
|
|
12
|
-
export { aiHostIn, aiModelIn, aiNameOf, annotatorOf, arrowOf, badges, closingOf, drawingsIn, drawingsText, gameReviewOf, isReviewed, reviewed, reviewOf, squareOf, unreviewed, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.js";
|
|
12
|
+
export { aiHostIn, aiModelIn, aiNameOf, annotatorOf, arrowOf, badges, closingOf, drawingsIn, drawingsText, gameReviewOf, isReviewed, reviewed, reviewOf, squareOf, unreviewed, unsigned, withAiHost, withAiModel, withAnnotator, withGameReview, } from "./review.js";
|
package/dist/core/review.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export declare function reviewed(move: RecordedMove, review: MoveReview): Record
|
|
|
51
51
|
* the game and who signed it. What the comments held besides stays, as do the NAGs of moves without a badge and the Annotator of a game it never reviewed.
|
|
52
52
|
*/
|
|
53
53
|
export declare function unreviewed(record: GameRecord): GameRecord;
|
|
54
|
+
/** The tags without who signed a review, the Annotator and its effort: the same game whoever reviewed it. */
|
|
55
|
+
export declare function unsigned(tags: Readonly<Record<string, string>>): Record<string, string>;
|
|
54
56
|
/** The arrows and squares as PGN's [%cal] and [%csl] write them, for a comment; empty without any. */
|
|
55
57
|
export declare function drawingsText({ arrows, squares }: Pick<MoveReview, "arrows" | "squares">): string;
|
|
56
58
|
/** The record with who reviewed it (D-81): the model, else the host, as PGN's Annotator, and the effort only when known; an effort no longer known goes. */
|
package/dist/core/review.js
CHANGED
|
@@ -64,9 +64,13 @@ export function unreviewed(record) {
|
|
|
64
64
|
});
|
|
65
65
|
const comment = withoutReview(record.comment);
|
|
66
66
|
// A game from elsewhere keeps its own Annotator.
|
|
67
|
-
const tags = carriesReview(record) ?
|
|
67
|
+
const tags = carriesReview(record) ? unsigned(record.tags) : record.tags;
|
|
68
68
|
return comment === "" ? { tags, moves } : { tags, comment, moves };
|
|
69
69
|
}
|
|
70
|
+
/** The tags without who signed a review, the Annotator and its effort: the same game whoever reviewed it. */
|
|
71
|
+
export function unsigned(tags) {
|
|
72
|
+
return Object.fromEntries(Object.entries(tags).filter(([name]) => name !== "Annotator" && name !== effortTag));
|
|
73
|
+
}
|
|
70
74
|
/** The arrows and squares as PGN's [%cal] and [%csl] write them, for a comment; empty without any. */
|
|
71
75
|
export function drawingsText({ arrows = [], squares = [] }) {
|
|
72
76
|
const arrowList = arrows.map(({ colour, from, to }) => `${letterOf(colour)}${from}${to}`).join(",");
|
package/dist/games/ai-model.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { AiModel } from "../core/index.ts";
|
|
|
3
3
|
export declare function noteAiModel(folder: string, noted: AiModel): boolean;
|
|
4
4
|
/** The AI's model last noted for the games of a folder in this process; none on the command line, or before the AI's first move. */
|
|
5
5
|
export declare function aiModelOf(folder: string): AiModel | undefined;
|
|
6
|
-
/** Notes the host the AI plays in for the games of a folder, after its move (D-97), as the model. */
|
|
7
|
-
export declare function noteAiHost(folder: string, host: string):
|
|
6
|
+
/** Notes the host the AI plays in for the games of a folder, after its move (D-97), as the model. Whether it changed. */
|
|
7
|
+
export declare function noteAiHost(folder: string, host: string): boolean;
|
|
8
|
+
/** Forgets who played as the AI for the games of a folder: the chat that did has left the table (D-104), and the next may be another. */
|
|
9
|
+
export declare function forgetTheAi(folder: string): void;
|
|
8
10
|
/** The host the AI played its last move in, for the games of a folder in this process; none on the command line, or before the AI's first move. */
|
|
9
11
|
export declare function aiHostOf(folder: string): string | undefined;
|
package/dist/games/ai-model.js
CHANGED
|
@@ -11,9 +11,18 @@ export function noteAiModel(folder, noted) {
|
|
|
11
11
|
export function aiModelOf(folder) {
|
|
12
12
|
return models.get(folder);
|
|
13
13
|
}
|
|
14
|
-
/** Notes the host the AI plays in for the games of a folder, after its move (D-97), as the model. */
|
|
14
|
+
/** Notes the host the AI plays in for the games of a folder, after its move (D-97), as the model. Whether it changed. */
|
|
15
15
|
export function noteAiHost(folder, host) {
|
|
16
|
+
if (hosts.get(folder) === host) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
16
19
|
hosts.set(folder, host);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
/** Forgets who played as the AI for the games of a folder: the chat that did has left the table (D-104), and the next may be another. */
|
|
23
|
+
export function forgetTheAi(folder) {
|
|
24
|
+
models.delete(folder);
|
|
25
|
+
hosts.delete(folder);
|
|
17
26
|
}
|
|
18
27
|
/** The host the AI played its last move in, for the games of a folder in this process; none on the command line, or before the AI's first move. */
|
|
19
28
|
export function aiHostOf(folder) {
|
package/dist/games/analysis.d.ts
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The game in analysis: its name as the lists write it, when the analysis last changed it, whether the player brought it on the page and the AI has not
|
|
3
|
-
* gone over it yet,
|
|
3
|
+
* gone over it yet, the moves whose note the chat corrected with the player, which the analyst leaves (D-101), and the run of the analyst that took it.
|
|
4
4
|
*/
|
|
5
5
|
export interface Analysis {
|
|
6
6
|
readonly game: string;
|
|
7
7
|
readonly at: string;
|
|
8
8
|
readonly brought?: true;
|
|
9
9
|
readonly corrected?: readonly string[];
|
|
10
|
+
readonly analyst?: AnalystRun;
|
|
10
11
|
}
|
|
11
|
-
/**
|
|
12
|
-
export
|
|
12
|
+
/** The run of the analyst agent that took a game, as the host names it, the game it took and when. */
|
|
13
|
+
export interface AnalystRun {
|
|
14
|
+
readonly game: string;
|
|
15
|
+
readonly run: string;
|
|
16
|
+
readonly since: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How a game is named in analysis: brought to the page by the player, with the moves the chat just corrected, taken by a run of the analyst, or afresh,
|
|
20
|
+
* without the corrections and the analyst it had.
|
|
21
|
+
*/
|
|
22
|
+
export interface AnalysisStart {
|
|
23
|
+
readonly brought?: boolean;
|
|
24
|
+
readonly corrected?: readonly string[];
|
|
25
|
+
readonly analyst?: AnalystRun;
|
|
26
|
+
readonly afresh?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Names the game in analysis, over the one before; written again each time its review changes, so the page hears of it. The same game keeps its corrections
|
|
30
|
+
* unless it starts afresh, in one write, so that the page never sees it leave the analysis; the analyst stays with the game it took, whichever the page
|
|
31
|
+
* opens meanwhile (Federico), and goes when that game starts afresh.
|
|
32
|
+
*/
|
|
33
|
+
export declare function startAnalysis(folder: string, game: string, now: Date, { brought, corrected, analyst, afresh }?: AnalysisStart): void;
|
|
34
|
+
/** Lets the analyst go from the game it took, when that game's review is deleted while the page shows another. */
|
|
35
|
+
export declare function forgetAnalyst(folder: string): void;
|
|
13
36
|
/** The game in analysis, or none when there is none, or analysis.json cannot be read. */
|
|
14
37
|
export declare function gameInAnalysis(folder: string): Analysis | undefined;
|
|
15
38
|
/** Ends the analysis, when a new game starts or its review is deleted. */
|
package/dist/games/analysis.js
CHANGED
|
@@ -3,12 +3,31 @@
|
|
|
3
3
|
import { readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { writeAtomically } from "./files.js";
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Names the game in analysis, over the one before; written again each time its review changes, so the page hears of it. The same game keeps its corrections
|
|
8
|
+
* unless it starts afresh, in one write, so that the page never sees it leave the analysis; the analyst stays with the game it took, whichever the page
|
|
9
|
+
* opens meanwhile (Federico), and goes when that game starts afresh.
|
|
10
|
+
*/
|
|
11
|
+
export function startAnalysis(folder, game, now, { brought = false, corrected = [], analyst, afresh = false } = {}) {
|
|
8
12
|
const before = gameInAnalysis(folder);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
13
|
+
const same = !afresh && before?.game === game;
|
|
14
|
+
const kept = [...new Set([...(same ? before.corrected ?? [] : []), ...corrected])];
|
|
15
|
+
const held = afresh && before?.analyst?.game === game ? undefined : before?.analyst;
|
|
16
|
+
writeAnalysis(folder, {
|
|
17
|
+
game,
|
|
18
|
+
at: now.toISOString(),
|
|
19
|
+
...(brought ? { brought: true } : {}),
|
|
20
|
+
...(kept.length === 0 ? {} : { corrected: kept }),
|
|
21
|
+
...withAnalyst(analyst ?? held),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** Lets the analyst go from the game it took, when that game's review is deleted while the page shows another. */
|
|
25
|
+
export function forgetAnalyst(folder) {
|
|
26
|
+
const analysis = gameInAnalysis(folder);
|
|
27
|
+
if (analysis?.analyst !== undefined) {
|
|
28
|
+
const { game, at, brought, corrected } = analysis;
|
|
29
|
+
writeAnalysis(folder, { game, at, ...(brought === undefined ? {} : { brought }), ...(corrected === undefined ? {} : { corrected }) });
|
|
30
|
+
}
|
|
12
31
|
}
|
|
13
32
|
/** The game in analysis, or none when there is none, or analysis.json cannot be read. */
|
|
14
33
|
export function gameInAnalysis(folder) {
|
|
@@ -16,7 +35,13 @@ export function gameInAnalysis(folder) {
|
|
|
16
35
|
const analysis = JSON.parse(readFileSync(join(folder, fileName), "utf8"));
|
|
17
36
|
const corrected = Array.isArray(analysis.corrected) ? analysis.corrected.filter((move) => typeof move === "string") : [];
|
|
18
37
|
return typeof analysis.game === "string" && typeof analysis.at === "string"
|
|
19
|
-
? {
|
|
38
|
+
? {
|
|
39
|
+
game: analysis.game,
|
|
40
|
+
at: analysis.at,
|
|
41
|
+
...(analysis.brought === true ? { brought: true } : {}),
|
|
42
|
+
...(corrected.length === 0 ? {} : { corrected }),
|
|
43
|
+
...withAnalyst(isAnalystRun(analysis.analyst) ? analysis.analyst : undefined),
|
|
44
|
+
}
|
|
20
45
|
: undefined;
|
|
21
46
|
}
|
|
22
47
|
catch {
|
|
@@ -28,3 +53,13 @@ export function endAnalysis(folder) {
|
|
|
28
53
|
rmSync(join(folder, fileName), { force: true });
|
|
29
54
|
}
|
|
30
55
|
const fileName = "analysis.json";
|
|
56
|
+
function writeAnalysis(folder, analysis) {
|
|
57
|
+
writeAtomically(join(folder, fileName), `${JSON.stringify(analysis, null, 2)}\n`);
|
|
58
|
+
}
|
|
59
|
+
function withAnalyst(analyst) {
|
|
60
|
+
return analyst === undefined ? {} : { analyst: { game: analyst.game, run: analyst.run, since: analyst.since } };
|
|
61
|
+
}
|
|
62
|
+
function isAnalystRun(value) {
|
|
63
|
+
const run = value;
|
|
64
|
+
return typeof run?.game === "string" && typeof run.run === "string" && typeof run.since === "string";
|
|
65
|
+
}
|
package/dist/games/archive.d.ts
CHANGED
|
@@ -11,10 +11,13 @@ export declare function isOverOnTable(folder: string, name: string): boolean;
|
|
|
11
11
|
export declare function startOfNewGame(folder: string, now: Date, before?: CurrentGame): Date;
|
|
12
12
|
/**
|
|
13
13
|
* Writes the game to the archive, with the plans, how it ended and the AI's model and host last noted (D-82, D-97), and gives its name; the same game again rewrites the
|
|
14
|
-
* same file.
|
|
14
|
+
* same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
15
|
+
*/
|
|
16
|
+
export declare function archiveGame(folder: string, current: CurrentGame): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Archives a game that has ended: its name in the archive, none for a game closed before any move, or, when that fails, a warning, since the game is saved in
|
|
19
|
+
* game.json all the same.
|
|
15
20
|
*/
|
|
16
|
-
export declare function archiveGame(folder: string, current: CurrentGame): string;
|
|
17
|
-
/** Archives a game that has ended: its name in the archive or, when that fails, a warning, since the game is saved in game.json all the same. */
|
|
18
21
|
export declare function archiveEnded(folder: string, current: CurrentGame): {
|
|
19
22
|
archive?: string;
|
|
20
23
|
warning?: string;
|
package/dist/games/archive.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The finished games, as PGN in games/ (D-9, D-41), with the AI's plans for the analyst (D-21); and the games played elsewhere, kept to study in study/ (D-68).
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { movesOnly, pgnDate, readPgn, withAiHost, withAiModel, writePgn } from "../core/index.js";
|
|
4
|
+
import { movesOnly, pgnDate, readPgn, unsigned, withAiHost, withAiModel, writePgn } from "../core/index.js";
|
|
5
5
|
import { aiHostOf, aiModelOf } from "./ai-model.js";
|
|
6
6
|
import { coachWords, withCoach } from "./coach.js";
|
|
7
7
|
import { endOf, loadGame } from "./current-game.js";
|
|
@@ -35,9 +35,12 @@ export function startOfNewGame(folder, now, before) {
|
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Writes the game to the archive, with the plans, how it ended and the AI's model and host last noted (D-82, D-97), and gives its name; the same game again rewrites the
|
|
38
|
-
* same file.
|
|
38
|
+
* same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
39
39
|
*/
|
|
40
40
|
export function archiveGame(folder, current) {
|
|
41
|
+
if (current.game.record.moves.length === 0) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
41
44
|
const name = archiveName(current);
|
|
42
45
|
const path = join(folder, name);
|
|
43
46
|
try {
|
|
@@ -49,10 +52,14 @@ export function archiveGame(folder, current) {
|
|
|
49
52
|
}
|
|
50
53
|
return name;
|
|
51
54
|
}
|
|
52
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Archives a game that has ended: its name in the archive, none for a game closed before any move, or, when that fails, a warning, since the game is saved in
|
|
57
|
+
* game.json all the same.
|
|
58
|
+
*/
|
|
53
59
|
export function archiveEnded(folder, current) {
|
|
54
60
|
try {
|
|
55
|
-
|
|
61
|
+
const archive = archiveGame(folder, current);
|
|
62
|
+
return archive === undefined ? {} : { archive };
|
|
56
63
|
}
|
|
57
64
|
catch (error) {
|
|
58
65
|
return { warning: `${error.message} The game is saved in game.json: it goes to the archive when a new game starts.` };
|
|
@@ -225,7 +232,8 @@ function gameNamed(folder, shelf, name) {
|
|
|
225
232
|
}
|
|
226
233
|
/** Whether two records are the same game: the same tags and the same moves, whatever their comments say. */
|
|
227
234
|
function sameGame(one, other) {
|
|
228
|
-
|
|
235
|
+
// Who signed a review is not the game: one the AI reviewed, or brought again with Lichess's Annotator, is the one kept.
|
|
236
|
+
const shape = (record) => JSON.stringify([record === undefined ? undefined : unsigned(record.tags), record?.moves.map((move) => move.san)]);
|
|
229
237
|
return one !== undefined && shape(one) === shape(other);
|
|
230
238
|
}
|
|
231
239
|
function nameAt(date, shelf = "games") {
|
|
@@ -41,8 +41,17 @@ export interface GameEnd {
|
|
|
41
41
|
}
|
|
42
42
|
/** The current game of a folder, as it is on disk, or undefined when there is none. A game.json Chesspal cannot read throws: nothing overwrites it. */
|
|
43
43
|
export declare function loadGame(folder: string): CurrentGame | undefined;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Writes the current game to game.json, whole or not at all; from then on it is the one kept in memory. Refused with GameChanged when another process wrote
|
|
46
|
+
* the file since this one read it: the change made on the game read before would lose theirs.
|
|
47
|
+
*/
|
|
45
48
|
export declare function saveGame(folder: string, current: CurrentGame): void;
|
|
49
|
+
/** The refusal of a save on a game another process changed since this one read it: what wrote it reads it again and starts over, once (rerunIfChanged). */
|
|
50
|
+
export declare class GameChanged extends Error {
|
|
51
|
+
constructor();
|
|
52
|
+
}
|
|
53
|
+
/** Runs a change of the game, and once more from the start when another process changed the game under it: a second refusal stands. */
|
|
54
|
+
export declare function rerunIfChanged<T>(run: () => T): T;
|
|
46
55
|
/** How the game ended, or undefined while it goes on: by the board, or by what the record says, such as a resignation. */
|
|
47
56
|
export declare function endOf(game: Game): GameEnd | undefined;
|
|
48
57
|
/** The draw offer that stands: none once the game is over (D-65). */
|
|
@@ -4,7 +4,7 @@ import { readFileSync, rmSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { Game, readPgn, writePgn } from "../core/index.js";
|
|
7
|
-
import { stampOf, writeAtomically } from "./files.js";
|
|
7
|
+
import { stampOf, whileLocked, writeAtomically } from "./files.js";
|
|
8
8
|
/** The current game of a folder, as it is on disk, or undefined when there is none. A game.json Chesspal cannot read throws: nothing overwrites it. */
|
|
9
9
|
export function loadGame(folder) {
|
|
10
10
|
const path = join(folder, fileName);
|
|
@@ -21,27 +21,41 @@ export function loadGame(folder) {
|
|
|
21
21
|
kept.set(path, { stamp, current });
|
|
22
22
|
return current;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Writes the current game to game.json, whole or not at all; from then on it is the one kept in memory. Refused with GameChanged when another process wrote
|
|
26
|
+
* the file since this one read it: the change made on the game read before would lose theirs.
|
|
27
|
+
*/
|
|
25
28
|
export function saveGame(folder, current) {
|
|
26
29
|
const path = join(folder, fileName);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
whileLocked(path, () => {
|
|
31
|
+
if (stampOf(path) !== kept.get(path)?.stamp) {
|
|
32
|
+
kept.delete(path);
|
|
33
|
+
throw new GameChanged();
|
|
34
|
+
}
|
|
35
|
+
write(path, current);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** The refusal of a save on a game another process changed since this one read it: what wrote it reads it again and starts over, once (rerunIfChanged). */
|
|
39
|
+
export class GameChanged extends Error {
|
|
40
|
+
constructor() {
|
|
41
|
+
super("The game changed on disk while this call was changing it, in another chat or on the board page: call it again. Nothing changed.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Runs a change of the game, and once more from the start when another process changed the game under it: a second refusal stands. */
|
|
45
|
+
export function rerunIfChanged(run) {
|
|
46
|
+
const again = (error) => {
|
|
47
|
+
if (!(error instanceof GameChanged)) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
return run();
|
|
35
51
|
};
|
|
36
52
|
try {
|
|
37
|
-
|
|
53
|
+
const result = run();
|
|
54
|
+
return result instanceof Promise ? result.catch(again) : result;
|
|
38
55
|
}
|
|
39
56
|
catch (error) {
|
|
40
|
-
|
|
41
|
-
kept.delete(path);
|
|
42
|
-
throw new Error(`Chesspal could not save the game: ${error.message} Nothing changed.`, { cause: error });
|
|
57
|
+
return again(error);
|
|
43
58
|
}
|
|
44
|
-
kept.set(path, { stamp: stampOf(path) ?? "", current });
|
|
45
59
|
}
|
|
46
60
|
/** How the game ended, or undefined while it goes on: by the board, or by what the record says, such as a resignation. */
|
|
47
61
|
export function endOf(game) {
|
|
@@ -100,6 +114,26 @@ const gameFile = z.object({
|
|
|
100
114
|
});
|
|
101
115
|
// The games read or written in this process, by their file, with the file's stamp then: a call reads a file again only when another process changed it.
|
|
102
116
|
const kept = new Map();
|
|
117
|
+
function write(path, current) {
|
|
118
|
+
const file = {
|
|
119
|
+
format: 1,
|
|
120
|
+
ai: current.ai,
|
|
121
|
+
started: current.started,
|
|
122
|
+
pgn: writePgn(current.game.record),
|
|
123
|
+
...(current.talk.length === 0 ? {} : { talk: current.talk }),
|
|
124
|
+
...(current.clock === undefined ? {} : { clock: savedClockOf(current.clock) }),
|
|
125
|
+
...(drawOfferOf(current) === undefined ? {} : { drawOffer: current.drawOffer }),
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
writeAtomically(path, `${JSON.stringify(file, null, 2)}\n`);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// The live game may hold a move the file does not: the next call reads the file again.
|
|
132
|
+
kept.delete(path);
|
|
133
|
+
throw new Error(`Chesspal could not save the game: ${error.message} Nothing changed.`, { cause: error });
|
|
134
|
+
}
|
|
135
|
+
kept.set(path, { stamp: stampOf(path) ?? "", current });
|
|
136
|
+
}
|
|
103
137
|
function parsed(text, path) {
|
|
104
138
|
try {
|
|
105
139
|
const file = gameFile.parse(JSON.parse(text));
|
package/dist/games/files.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** Writes a file whole or not at all: a temporary file beside it, then renamed over it (D-27). A failure says what to do about it. */
|
|
2
2
|
export declare function writeAtomically(path: string, text: string | Uint8Array): void;
|
|
3
|
+
/**
|
|
4
|
+
* Runs `write` while this process alone holds the file's lock, a folder beside it made only if missing, cheaper than a file on Windows: another process
|
|
5
|
+
* waits, a second at most. A lock older than two seconds is of a process that stopped while it held it, and goes.
|
|
6
|
+
*/
|
|
7
|
+
export declare function whileLocked<T>(path: string, write: () => T): T;
|
|
3
8
|
/** Makes a folder, and those above it, when missing. A failure says what to do about it. */
|
|
4
9
|
export declare function makeFolder(path: string): void;
|
|
5
10
|
/** A stamp of a file that changes whenever it is written, whoever writes it; undefined when there is none. */
|