chesspal 0.3.6 → 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.
- package/dist/cli/cli.js +7 -2
- package/dist/cli/command-line.js +5 -3
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.js +8 -14
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +7 -5
- package/dist/commands/answers/agents.js +16 -9
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +14 -7
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +5 -2
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/new.js +41 -11
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +4 -1
- package/dist/commands/game/status.d.ts +1 -1
- package/dist/commands/game/status.js +6 -6
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +1 -1
- package/dist/commands/profiles/past-games.js +21 -10
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +6 -5
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +2 -1
- package/dist/games/index.js +2 -1
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/home/same-path.d.ts +6 -1
- package/dist/home/same-path.js +22 -3
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +13 -4
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/app.js +30 -7
- package/dist/page/app/served.js +9 -4
- package/dist/page/avatars/torretta.css +4 -4
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +4 -2
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +86 -6
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +32 -15
- package/dist/page/layout/window.js +41 -2
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -9
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +24 -8
- package/dist/server/bell/bell.d.ts +24 -27
- package/dist/server/bell/bell.js +79 -143
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +78 -13
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +38 -4
- package/dist/server/page/table.js +61 -23
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +6 -4
- package/dist/server/table/hosts/play.js +58 -16
- package/dist/server/table/index.d.ts +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +1 -0
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +39 -11
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
package/dist/core/openings.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Chess } from "chess.js";
|
|
3
3
|
/** No line of the book is longer: past it, no position is looked up. */
|
|
4
4
|
const bookDepth = 40;
|
|
5
|
+
const standardStart = positionKey(new Chess().fen());
|
|
5
6
|
/** The index of the openings in Lichess's TSV files (eco, name, pgn), each line keyed by the position its moves reach. */
|
|
6
7
|
export function indexOpenings(tsv) {
|
|
7
8
|
const index = {};
|
|
@@ -37,39 +38,61 @@ export function indexBook(tsv) {
|
|
|
37
38
|
export function bookMoves(fen, book) {
|
|
38
39
|
return book[positionKey(fen)] ?? [];
|
|
39
40
|
}
|
|
40
|
-
/** Whether a line of the book reaches a position, by any order of moves: one its lines play on from, or one an opening is named after. */
|
|
41
|
-
export function inOpenings(fen, index, book) {
|
|
42
|
-
const key = positionKey(fen);
|
|
43
|
-
return book[key] !== undefined || index[key] !== undefined;
|
|
44
|
-
}
|
|
45
41
|
/** The opening of a game from the standard position, the last named one its moves reached; none before the first, or from another position. */
|
|
46
42
|
export function openingOf(record, index) {
|
|
47
|
-
|
|
43
|
+
return bookPlaceOf(positionKeysOf(record), index)?.opening;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Where a game stands in the book, from the keys of the positions it stood in, its start first: the last named position its moves reached, and its first
|
|
47
|
+
* plies whose position a line of the book reaches, by any order of moves, one its lines play on from or one an opening is named after; without the book,
|
|
48
|
+
* no ply is counted. None from another position than the standard one, whatever its FEN tag says.
|
|
49
|
+
*/
|
|
50
|
+
export function bookPlaceOf(keys, index, book) {
|
|
51
|
+
if (!standardStartKey(keys[0])) {
|
|
48
52
|
return undefined;
|
|
49
53
|
}
|
|
50
|
-
const
|
|
51
|
-
const plies = Math.min(record.moves.length, bookDepth);
|
|
54
|
+
const last = Math.min(keys.length - 1, bookDepth);
|
|
52
55
|
let found;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
+
let plies = 0;
|
|
57
|
+
for (let ply = 1; ply <= last; ply++) {
|
|
58
|
+
const key = keys[ply];
|
|
59
|
+
const named = index[key];
|
|
56
60
|
if (named !== undefined) {
|
|
57
61
|
found = { named, ply };
|
|
58
62
|
}
|
|
63
|
+
if (book !== undefined && plies === ply - 1 && (named !== undefined || book[key] !== undefined)) {
|
|
64
|
+
plies = ply;
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
return found === undefined ? { plies } : { opening: openingNamed(found.named, found.ply), plies };
|
|
68
|
+
}
|
|
69
|
+
/** The keys of a record's positions, its start first, replayed to the book's depth for a game from the standard position: for a record no live game holds. */
|
|
70
|
+
export function positionKeysOf(record) {
|
|
71
|
+
const start = positionKey(record.tags.FEN ?? new Chess().fen());
|
|
72
|
+
if (!standardStartKey(start)) {
|
|
73
|
+
return [start];
|
|
62
74
|
}
|
|
63
|
-
const
|
|
75
|
+
const chess = new Chess();
|
|
76
|
+
return [start, ...record.moves.slice(0, bookDepth).map(({ san }) => positionKey(chess.move(san).after))];
|
|
77
|
+
}
|
|
78
|
+
/** A position without its move counters, the key of the index and the book: the same position reached in another order is the same key. */
|
|
79
|
+
export function positionKey(fen) {
|
|
80
|
+
return fen.split(" ").slice(0, 4).join(" ");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The move number at which a game from the standard position left the book (D-68), the move of its first ply past the `inBook` plies a line of the book
|
|
84
|
+
* reaches; none while all its `plies` are the book's.
|
|
85
|
+
*/
|
|
86
|
+
export function outOfBookMove(plies, inBook) {
|
|
87
|
+
return plies > inBook ? Math.floor(inBook / 2) + 1 : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** The opening a name of the index stands for, reached at `ply`. */
|
|
90
|
+
function openingNamed([eco, name], ply) {
|
|
64
91
|
const colon = name.indexOf(":");
|
|
65
92
|
const family = colon < 0 ? name : name.slice(0, colon);
|
|
66
|
-
const opening = { eco, name, family, ply
|
|
93
|
+
const opening = { eco, name, family, ply };
|
|
67
94
|
return colon < 0 ? opening : { ...opening, variation: name.slice(colon + 1).trim() };
|
|
68
95
|
}
|
|
69
|
-
/** The move number at which a game that reached an opening at `ply` left the book. */
|
|
70
|
-
export function outOfBookMove(opening) {
|
|
71
|
-
return Math.floor(opening.ply / 2) + 1;
|
|
72
|
-
}
|
|
73
96
|
/** The moves from a position that at least one line in fifty plays, the most played first, with the trap's name when all their lines are traps. */
|
|
74
97
|
function bookOf(counts) {
|
|
75
98
|
const total = [...counts.values()].reduce((sum, count) => sum + count.lines, 0);
|
|
@@ -92,7 +115,7 @@ function linesOf(tsv) {
|
|
|
92
115
|
return [{ eco, name, moves }];
|
|
93
116
|
});
|
|
94
117
|
}
|
|
95
|
-
/**
|
|
96
|
-
function
|
|
97
|
-
return
|
|
118
|
+
/** Whether a position's key is the standard start's: the one check of where the book applies. */
|
|
119
|
+
function standardStartKey(key) {
|
|
120
|
+
return key === standardStart;
|
|
98
121
|
}
|
package/dist/core/pgn.d.ts
CHANGED
|
@@ -6,5 +6,9 @@ import { type GameRecord } from "./record.ts";
|
|
|
6
6
|
export declare function writePgn(record: GameRecord): string;
|
|
7
7
|
/** The games of a PGN text, with their tags, moves, NAGs (! and ? too) and comments; variations are skipped. Every move is checked: an error names its line. */
|
|
8
8
|
export declare function readPgn(text: string): GameRecord[];
|
|
9
|
+
/** The games of a PGN text as readPgn gives them, but with their moves as written, neither checked nor played: for what the tags and comments say, quickly. */
|
|
10
|
+
export declare function readPgnUnplayed(text: string): GameRecord[];
|
|
11
|
+
/** The tags of a PGN text's first game, read without its moves: none checked, none played. */
|
|
12
|
+
export declare function readPgnTags(text: string): Readonly<Record<string, string>>;
|
|
9
13
|
/** The day of a date, in its own time zone, as PGN's Date tag writes it: 2026.11.02. */
|
|
10
14
|
export declare function pgnDate(date: Date): string;
|
package/dist/core/pgn.js
CHANGED
|
@@ -14,7 +14,25 @@ export function writePgn(record) {
|
|
|
14
14
|
}
|
|
15
15
|
/** The games of a PGN text, with their tags, moves, NAGs (! and ? too) and comments; variations are skipped. Every move is checked: an error names its line. */
|
|
16
16
|
export function readPgn(text) {
|
|
17
|
-
return new PgnReader(text).read();
|
|
17
|
+
return new PgnReader(text, true).read();
|
|
18
|
+
}
|
|
19
|
+
/** The games of a PGN text as readPgn gives them, but with their moves as written, neither checked nor played: for what the tags and comments say, quickly. */
|
|
20
|
+
export function readPgnUnplayed(text) {
|
|
21
|
+
return new PgnReader(text, false).read();
|
|
22
|
+
}
|
|
23
|
+
/** The tags of a PGN text's first game, read without its moves: none checked, none played. */
|
|
24
|
+
export function readPgnTags(text) {
|
|
25
|
+
const tags = {};
|
|
26
|
+
for (const line of text.split("\n")) {
|
|
27
|
+
const tag = tagPattern.exec(line);
|
|
28
|
+
if (tag !== null) {
|
|
29
|
+
tags[tag[1]] = tag[2].replace(/\\(.)/g, "$1");
|
|
30
|
+
}
|
|
31
|
+
else if (line.trim() !== "" || Object.keys(tags).length > 0) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return tags;
|
|
18
36
|
}
|
|
19
37
|
/** The day of a date, in its own time zone, as PGN's Date tag writes it: 2026.11.02. */
|
|
20
38
|
export function pgnDate(date) {
|
|
@@ -24,6 +42,8 @@ export function pgnDate(date) {
|
|
|
24
42
|
// The seven tags of every PGN, in their order, with the value for an unknown one.
|
|
25
43
|
const sevenTags = [["Event", "?"], ["Site", "?"], ["Date", "????.??.??"], ["Round", "?"], ["White", "?"], ["Black", "?"], ["Result", "*"]];
|
|
26
44
|
const sevenTagNames = new Set(sevenTags.map(([name]) => name));
|
|
45
|
+
// A line holding a tag alone, as writePgn writes it.
|
|
46
|
+
const tagPattern = /^\s*\[\s*(\w+)\s*"((?:[^"\\]|\\.)*)"\s*\]\s*$/;
|
|
27
47
|
// The judgments written after a move, NAGs $1 to $6.
|
|
28
48
|
const glyphs = ["!", "?", "!!", "??", "!?", "?!"];
|
|
29
49
|
const nagOfGlyph = new Map(glyphs.map((glyph, index) => [glyph, index + 1]));
|
|
@@ -33,16 +53,20 @@ const lineLength = 79;
|
|
|
33
53
|
const tokenPattern = /\s+|(?<![^\n])%[^\n]*|;[^\n]*|\[\s*(\w+)\s*"((?:[^"\\]|\\.)*)"\s*\]|\{([^}]*)\}|\$(\d+)|([()])|\d+\s*\.+|\.+|(1-0|0-1|1\/2-1\/2|\*)|([^\s{}()[\]";$]+)/y;
|
|
34
54
|
class PgnReader {
|
|
35
55
|
text;
|
|
56
|
+
// Whether the moves are played, so checked and written as Chesspal writes them, or taken as written.
|
|
57
|
+
plays;
|
|
36
58
|
records = [];
|
|
37
59
|
tags = [];
|
|
38
60
|
comments = [];
|
|
39
61
|
moves = [];
|
|
40
62
|
game;
|
|
63
|
+
begun = false;
|
|
41
64
|
result;
|
|
42
65
|
depth = 0;
|
|
43
66
|
at = 0;
|
|
44
|
-
constructor(text) {
|
|
67
|
+
constructor(text, plays) {
|
|
45
68
|
this.text = text;
|
|
69
|
+
this.plays = plays;
|
|
46
70
|
}
|
|
47
71
|
read() {
|
|
48
72
|
for (let index = 0; index < this.text.length; index = tokenPattern.lastIndex) {
|
|
@@ -94,7 +118,7 @@ class PgnReader {
|
|
|
94
118
|
}
|
|
95
119
|
tag(name, value) {
|
|
96
120
|
// A tag after the moves starts the next game, when the last one had no result.
|
|
97
|
-
if (this.
|
|
121
|
+
if (this.begun) {
|
|
98
122
|
this.finish();
|
|
99
123
|
}
|
|
100
124
|
this.tags.push([name, value]);
|
|
@@ -106,7 +130,10 @@ class PgnReader {
|
|
|
106
130
|
}
|
|
107
131
|
const [, san = "", glyph = ""] = /^(.*?)([!?]*)$/.exec(token) ?? [];
|
|
108
132
|
const game = this.started();
|
|
109
|
-
if (san !== "") {
|
|
133
|
+
if (san !== "" && game === undefined) {
|
|
134
|
+
this.moves.push({ san, nags: [], comments: [] });
|
|
135
|
+
}
|
|
136
|
+
else if (san !== "" && game !== undefined) {
|
|
110
137
|
const played = game.playRecorded(san);
|
|
111
138
|
if (!played.played) {
|
|
112
139
|
throw this.error(this.at, played.message, game.position.moveNumber);
|
|
@@ -129,9 +156,10 @@ class PgnReader {
|
|
|
129
156
|
// A NAG before the first move judges nothing: skipped.
|
|
130
157
|
this.moves.at(-1)?.nags.push(nag);
|
|
131
158
|
}
|
|
132
|
-
/** The game the moves are played on, made from the tags at the first move. */
|
|
159
|
+
/** The game the moves are played on, made from the tags at the first move; none when they are taken as written. */
|
|
133
160
|
started() {
|
|
134
|
-
|
|
161
|
+
this.begun = true;
|
|
162
|
+
if (this.game === undefined && this.plays) {
|
|
135
163
|
try {
|
|
136
164
|
this.game = new Game({ tags: Object.fromEntries(this.tags), moves: [] });
|
|
137
165
|
}
|
|
@@ -156,6 +184,7 @@ class PgnReader {
|
|
|
156
184
|
this.comments = [];
|
|
157
185
|
this.moves = [];
|
|
158
186
|
this.game = undefined;
|
|
187
|
+
this.begun = false;
|
|
159
188
|
this.result = undefined;
|
|
160
189
|
}
|
|
161
190
|
error(index, message, moveNumber) {
|
package/dist/games/archive.d.ts
CHANGED
|
@@ -10,15 +10,15 @@ export declare function isOverOnTable(folder: string, name: string): boolean;
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function startOfNewGame(folder: string, now: Date, before?: CurrentGame): Date;
|
|
12
12
|
/**
|
|
13
|
-
* Writes the game to the archive, with the plans, how it ended
|
|
14
|
-
* same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
13
|
+
* Writes the game to the archive, with the plans, how it ended, the AI's model and host last noted (D-82, D-97) and the opening's tags, and gives its name;
|
|
14
|
+
* the same game again rewrites the same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
15
15
|
*/
|
|
16
|
-
export declare function archiveGame(folder: string, current: CurrentGame): string | undefined;
|
|
16
|
+
export declare function archiveGame(folder: string, current: CurrentGame, opening: OpeningTags): string | undefined;
|
|
17
17
|
/**
|
|
18
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
19
|
* game.json all the same.
|
|
20
20
|
*/
|
|
21
|
-
export declare function archiveEnded(folder: string, current: CurrentGame): {
|
|
21
|
+
export declare function archiveEnded(folder: string, current: CurrentGame, opening: OpeningTags): {
|
|
22
22
|
archive?: string;
|
|
23
23
|
warning?: string;
|
|
24
24
|
};
|
|
@@ -26,16 +26,36 @@ export declare function archiveEnded(folder: string, current: CurrentGame): {
|
|
|
26
26
|
* Writes the coach's judgments into a game over already in the archive, beside what the analyst may have written there since (Federico);
|
|
27
27
|
* archives the game when it is not there.
|
|
28
28
|
*/
|
|
29
|
-
export declare function archiveCoached(folder: string, current: CurrentGame): {
|
|
29
|
+
export declare function archiveCoached(folder: string, current: CurrentGame, opening: OpeningTags): {
|
|
30
30
|
archive?: string;
|
|
31
31
|
warning?: string;
|
|
32
32
|
};
|
|
33
33
|
/** The name of a game over in the archive, when it is there. */
|
|
34
34
|
export declare function archivedName(folder: string, current: CurrentGame): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The opening a game reached, in its ECO and Opening tags, as the commands name it: none from another position or outside the book. With the PlyCount the
|
|
37
|
+
* archive adds, a game is listed from its tags alone.
|
|
38
|
+
*/
|
|
39
|
+
export interface OpeningTags {
|
|
40
|
+
readonly ECO?: string;
|
|
41
|
+
readonly Opening?: string;
|
|
42
|
+
}
|
|
35
43
|
/** The names of the games in the archive, the latest first: games/2026-11-02-153012.pgn. */
|
|
36
44
|
export declare function archivedGames(folder: string): string[];
|
|
37
45
|
/** A game of the archive by its name, as listed; refused when it is not there, or cannot be read. */
|
|
38
46
|
export declare function archivedGame(folder: string, name: string): GameRecord;
|
|
47
|
+
/** A game of the archive by its name, as listed, with its moves as written, neither checked nor played: for its tags and comments, quickly. */
|
|
48
|
+
export declare function archivedGameUnplayed(folder: string, name: string): GameRecord;
|
|
49
|
+
/** The tags of a game of the archive by its name, as listed, read without its moves; refused as archivedGame refuses. */
|
|
50
|
+
export declare function archivedTags(folder: string, name: string): Readonly<Record<string, string>>;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the archive wrote a game's list tags, its PlyCount with its ECO and Opening, or none for a game that has no opening: a numeric PlyCount says so.
|
|
53
|
+
* A game an older Chesspal archived has none, and is read whole.
|
|
54
|
+
*/
|
|
55
|
+
export declare function hasListTags(tags: Readonly<Record<string, string>>): boolean;
|
|
56
|
+
/** The refusal of a game asked by a name no game has, apart from a game that is there and cannot be read. */
|
|
57
|
+
export declare class GameMissing extends Error {
|
|
58
|
+
}
|
|
39
59
|
/** The names of the games kept to study in a profile's folder, the latest kept first: study/2026-11-04-153012.pgn. */
|
|
40
60
|
export declare function keptGames(root: string): string[];
|
|
41
61
|
/** A game kept to study by its name, as listed; refused when it is not there, or cannot be read. */
|
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, unsigned, withAiHost, withAiModel, writePgn } from "../core/index.js";
|
|
4
|
+
import { movesOnly, pgnDate, readPgn, readPgnTags, readPgnUnplayed, 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";
|
|
@@ -34,10 +34,10 @@ export function startOfNewGame(folder, now, before) {
|
|
|
34
34
|
return start;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Writes the game to the archive, with the plans, how it ended
|
|
38
|
-
* same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
37
|
+
* Writes the game to the archive, with the plans, how it ended, the AI's model and host last noted (D-82, D-97) and the opening's tags, and gives its name;
|
|
38
|
+
* the same game again rewrites the same file. A game closed before any move is not kept, as Lichess aborts it: none.
|
|
39
39
|
*/
|
|
40
|
-
export function archiveGame(folder, current) {
|
|
40
|
+
export function archiveGame(folder, current, opening) {
|
|
41
41
|
if (current.game.record.moves.length === 0) {
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
@@ -45,7 +45,7 @@ export function archiveGame(folder, current) {
|
|
|
45
45
|
const path = join(folder, name);
|
|
46
46
|
try {
|
|
47
47
|
makeFolder(dirname(path));
|
|
48
|
-
writeAtomically(path, writePgn(withAiHost(withAiModel(endedRecord(folder, current), aiModelOf(folder)), aiHostOf(folder))));
|
|
48
|
+
writeAtomically(path, writePgn(withListTags(withAiHost(withAiModel(endedRecord(folder, current), aiModelOf(folder)), aiHostOf(folder)), opening)));
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
51
51
|
throw new Error(`Chesspal could not archive the game: ${error.message}`, { cause: error });
|
|
@@ -56,9 +56,9 @@ export function archiveGame(folder, current) {
|
|
|
56
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
57
|
* game.json all the same.
|
|
58
58
|
*/
|
|
59
|
-
export function archiveEnded(folder, current) {
|
|
59
|
+
export function archiveEnded(folder, current, opening) {
|
|
60
60
|
try {
|
|
61
|
-
const archive = archiveGame(folder, current);
|
|
61
|
+
const archive = archiveGame(folder, current, opening);
|
|
62
62
|
return archive === undefined ? {} : { archive };
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
@@ -69,10 +69,10 @@ export function archiveEnded(folder, current) {
|
|
|
69
69
|
* Writes the coach's judgments into a game over already in the archive, beside what the analyst may have written there since (Federico);
|
|
70
70
|
* archives the game when it is not there.
|
|
71
71
|
*/
|
|
72
|
-
export function archiveCoached(folder, current) {
|
|
72
|
+
export function archiveCoached(folder, current, opening) {
|
|
73
73
|
const name = archivedName(folder, current);
|
|
74
74
|
if (name === undefined) {
|
|
75
|
-
return archiveEnded(folder, current);
|
|
75
|
+
return archiveEnded(folder, current, opening);
|
|
76
76
|
}
|
|
77
77
|
try {
|
|
78
78
|
writeAtomically(join(folder, name), writePgn(withCoach(archivedGame(folder, name), coachWords(folder, current))));
|
|
@@ -98,6 +98,28 @@ export function archivedGames(folder) {
|
|
|
98
98
|
export function archivedGame(folder, name) {
|
|
99
99
|
return gameNamed(folder, "games", name);
|
|
100
100
|
}
|
|
101
|
+
/** A game of the archive by its name, as listed, with its moves as written, neither checked nor played: for its tags and comments, quickly. */
|
|
102
|
+
export function archivedGameUnplayed(folder, name) {
|
|
103
|
+
const [record] = readPgnUnplayed(gameText(folder, "games", name));
|
|
104
|
+
if (record === undefined) {
|
|
105
|
+
throw new Error(`${name} holds no game Chesspal can read.`);
|
|
106
|
+
}
|
|
107
|
+
return record;
|
|
108
|
+
}
|
|
109
|
+
/** The tags of a game of the archive by its name, as listed, read without its moves; refused as archivedGame refuses. */
|
|
110
|
+
export function archivedTags(folder, name) {
|
|
111
|
+
return readPgnTags(gameText(folder, "games", name));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Whether the archive wrote a game's list tags, its PlyCount with its ECO and Opening, or none for a game that has no opening: a numeric PlyCount says so.
|
|
115
|
+
* A game an older Chesspal archived has none, and is read whole.
|
|
116
|
+
*/
|
|
117
|
+
export function hasListTags(tags) {
|
|
118
|
+
return /^\d+$/.test(tags.PlyCount ?? "");
|
|
119
|
+
}
|
|
120
|
+
/** The refusal of a game asked by a name no game has, apart from a game that is there and cannot be read. */
|
|
121
|
+
export class GameMissing extends Error {
|
|
122
|
+
}
|
|
101
123
|
/** The names of the games kept to study in a profile's folder, the latest kept first: study/2026-11-04-153012.pgn. */
|
|
102
124
|
export function keptGames(root) {
|
|
103
125
|
return gameNames(root, "study");
|
|
@@ -211,24 +233,30 @@ function gameNames(folder, shelf) {
|
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
235
|
function gameNamed(folder, shelf, name) {
|
|
236
|
+
const [record] = readPgn(gameText(folder, shelf, name));
|
|
237
|
+
if (record === undefined) {
|
|
238
|
+
throw new Error(`${name} holds no game Chesspal can read.`);
|
|
239
|
+
}
|
|
240
|
+
return record;
|
|
241
|
+
}
|
|
242
|
+
function gameText(folder, shelf, name) {
|
|
214
243
|
// The name as listed, or without its folder or its .pgn.
|
|
215
244
|
const file = `${(name.startsWith(`${shelf}/`) ? name.slice(shelf.length + 1) : name).trim().replace(/\.pgn$/, "")}.pgn`;
|
|
216
245
|
const { what, list } = shelves[shelf];
|
|
217
246
|
if (!namePattern.test(file)) {
|
|
218
|
-
throw new
|
|
247
|
+
throw new GameMissing(`${name} is no game ${what}: give a name as ${list} writes it, such as ${shelf}/2026-11-02-153012.pgn.`);
|
|
219
248
|
}
|
|
220
|
-
let text;
|
|
221
249
|
try {
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
throw new Error(`There is no ${name} among the games ${what}: give a name as ${list} writes it.`);
|
|
250
|
+
return readFileSync(join(folder, shelf, file), "utf8");
|
|
226
251
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
252
|
+
catch (error) {
|
|
253
|
+
const code = error.code;
|
|
254
|
+
// Only a file that is not there is a missing game: one busy or locked by another process is there, and may be read in a moment.
|
|
255
|
+
if (code === "ENOENT") {
|
|
256
|
+
throw new GameMissing(`There is no ${name} among the games ${what}: give a name as ${list} writes it.`);
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`${name} could not be read (${code ?? error.message}): try again in a moment.`, { cause: error });
|
|
230
259
|
}
|
|
231
|
-
return record;
|
|
232
260
|
}
|
|
233
261
|
/** Whether two records are the same game: the same tags and the same moves, whatever their comments say. */
|
|
234
262
|
function sameGame(one, other) {
|
|
@@ -245,6 +273,11 @@ function endedRecord(folder, current) {
|
|
|
245
273
|
const { record, outcome } = current.game;
|
|
246
274
|
return withTermination(withCoach(record, coachWords(folder, current)), outcome);
|
|
247
275
|
}
|
|
276
|
+
/** The record with the tags past_games lists it from: the opening's, the commands' alone whatever the game brought, and how many plies it has. */
|
|
277
|
+
function withListTags(record, opening) {
|
|
278
|
+
const tags = Object.fromEntries(Object.entries(record.tags).filter(([name]) => name !== "ECO" && name !== "Opening" && name !== "PlyCount"));
|
|
279
|
+
return { ...record, tags: { ...tags, ...opening, PlyCount: String(record.moves.length) } };
|
|
280
|
+
}
|
|
248
281
|
function withTermination(record, outcome) {
|
|
249
282
|
return outcome.over && record.tags.Termination === undefined ? { ...record, tags: { ...record.tags, Termination: outcome.ending } } : record;
|
|
250
283
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How a chat started, as the host's SessionStart hook says it: new, cleared, resumed or compacted; when, as an ISO date; its session,
|
|
3
|
-
* file where the host writes the chat, where Claude Code tells which model made each tool call (D-81).
|
|
2
|
+
* How a chat started, as the host's SessionStart hook says it: new, cleared, resumed or compacted; when, as an ISO date; its session, as the hooks have it;
|
|
3
|
+
* and the file where the host writes the chat, where Claude Code tells which model made each tool call (D-81).
|
|
4
4
|
*/
|
|
5
5
|
export interface ChatStart {
|
|
6
6
|
readonly source: string;
|
package/dist/games/chat-start.js
CHANGED
|
@@ -13,7 +13,12 @@ export function lastChatStart(folder) {
|
|
|
13
13
|
if (typeof start.source !== "string" || typeof start.at !== "string") {
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
|
-
return {
|
|
16
|
+
return {
|
|
17
|
+
source: start.source,
|
|
18
|
+
at: start.at,
|
|
19
|
+
...(typeof start.session === "string" ? { session: start.session } : {}),
|
|
20
|
+
...(typeof start.transcript === "string" ? { transcript: start.transcript } : {}),
|
|
21
|
+
};
|
|
17
22
|
}
|
|
18
23
|
catch {
|
|
19
24
|
return undefined;
|
|
@@ -43,9 +43,9 @@ export interface GameEnd {
|
|
|
43
43
|
export declare function loadGame(folder: string): CurrentGame | undefined;
|
|
44
44
|
/**
|
|
45
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.
|
|
46
|
+
* the file since this one read it: the change made on the game read before would lose theirs. `first` runs just before the write, on the game read.
|
|
47
47
|
*/
|
|
48
|
-
export declare function saveGame(folder: string, current: CurrentGame): void;
|
|
48
|
+
export declare function saveGame(folder: string, current: CurrentGame, first?: () => void): void;
|
|
49
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
50
|
export declare class GameChanged extends Error {
|
|
51
51
|
constructor();
|
|
@@ -23,15 +23,16 @@ export function loadGame(folder) {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
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.
|
|
26
|
+
* the file since this one read it: the change made on the game read before would lose theirs. `first` runs just before the write, on the game read.
|
|
27
27
|
*/
|
|
28
|
-
export function saveGame(folder, current) {
|
|
28
|
+
export function saveGame(folder, current, first) {
|
|
29
29
|
const path = join(folder, fileName);
|
|
30
30
|
whileLocked(path, () => {
|
|
31
31
|
if (stampOf(path) !== kept.get(path)?.stamp) {
|
|
32
32
|
kept.delete(path);
|
|
33
33
|
throw new GameChanged();
|
|
34
34
|
}
|
|
35
|
+
first?.();
|
|
35
36
|
write(path, current);
|
|
36
37
|
});
|
|
37
38
|
}
|
package/dist/games/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { aiHostOf, aiModelOf, forgetTheAi, noteAiHost, noteAiModel } from "./ai-model.ts";
|
|
2
2
|
export { type Analysis, type AnalysisStart, endAnalysis, forgetAnalyst, gameInAnalysis, startAnalysis } from "./analysis.ts";
|
|
3
|
-
export { archiveCoached, archivedGame, archivedGames, archivedName, archivedWhen, archiveEnded, archiveGame, archiveName, deleteGame, gameStemOf, isGameName, isOverOnTable, keepToStudy, keptGame, keptGames, listedName, movesPgn, playerPgn, rewriteGame, startOfNewGame, } from "./archive.ts";
|
|
3
|
+
export { archiveCoached, archivedGame, archivedGames, archivedGameUnplayed, archivedName, archivedTags, archivedWhen, archiveEnded, archiveGame, archiveName, deleteGame, GameMissing, gameStemOf, hasListTags, isGameName, isOverOnTable, keepToStudy, keptGame, keptGames, listedName, movesPgn, type OpeningTags, playerPgn, rewriteGame, startOfNewGame, } from "./archive.ts";
|
|
4
4
|
export { type ChatStart, cleanSince, lastChatStart, saveChatStart } from "./chat-start.ts";
|
|
5
5
|
export { type CoachHint, type CoachJudgment, type CoachLead, coachShown, coachStarted, coachWaitingSince, type CoachWords, coachWords, forgetCoach, keepHint, keepJudgment, keepLead, startCoach, withCoach } from "./coach.ts";
|
|
6
6
|
export { bothSidesMoved, type CurrentGame, drawOfferOf, endOf, GameChanged, type GameEnd, gameStamp, loadGame, removeGame, rerunIfChanged, saveGame, type Shown, type Speaker, type TalkLine } from "./current-game.ts";
|
|
7
7
|
export { makeFolder, renameOver, writeAtomically } from "./files.ts";
|
|
8
8
|
export { commentWithPlan, currentPlan, planOf, withoutPlans } from "./plans.ts";
|
|
9
|
+
export { forgetPositionShown, hostProcess, notePositionShown, type PositionShown, positionShownNote } from "./position-shown.ts";
|
|
9
10
|
export { endTableTalk, loadTableTalk, saveTableTalk, type TableTalk, tableTalkStamp } from "./table-talk.ts";
|
|
10
11
|
export { type Heard, heardLines, type Sayer, type Talk, talkLimits, talkLine, unheardWords, wakesTheChat, type Words, WordsRefused } from "./talk.ts";
|
|
11
12
|
export { keepThought, notePondering, ponderingOn, ponderingUntil, type Thought, thoughtForLastMove, thoughtKey, waitingSince } from "./thoughts.ts";
|
package/dist/games/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// The player's games on disk: the current one in game.json, the finished ones in games/ (D-27, D-41), the games kept to study in study/ (D-68). Below the commands, above the core.
|
|
2
2
|
export { aiHostOf, aiModelOf, forgetTheAi, noteAiHost, noteAiModel } from "./ai-model.js";
|
|
3
3
|
export { endAnalysis, forgetAnalyst, gameInAnalysis, startAnalysis } from "./analysis.js";
|
|
4
|
-
export { archiveCoached, archivedGame, archivedGames, archivedName, archivedWhen, archiveEnded, archiveGame, archiveName, deleteGame, gameStemOf, isGameName, isOverOnTable, keepToStudy, keptGame, keptGames, listedName, movesPgn, playerPgn, rewriteGame, startOfNewGame, } from "./archive.js";
|
|
4
|
+
export { archiveCoached, archivedGame, archivedGames, archivedGameUnplayed, archivedName, archivedTags, archivedWhen, archiveEnded, archiveGame, archiveName, deleteGame, GameMissing, gameStemOf, hasListTags, isGameName, isOverOnTable, keepToStudy, keptGame, keptGames, listedName, movesPgn, playerPgn, rewriteGame, startOfNewGame, } from "./archive.js";
|
|
5
5
|
export { cleanSince, lastChatStart, saveChatStart } from "./chat-start.js";
|
|
6
6
|
export { coachShown, coachStarted, coachWaitingSince, coachWords, forgetCoach, keepHint, keepJudgment, keepLead, startCoach, withCoach } from "./coach.js";
|
|
7
7
|
export { bothSidesMoved, drawOfferOf, endOf, GameChanged, gameStamp, loadGame, removeGame, rerunIfChanged, saveGame } from "./current-game.js";
|
|
8
8
|
export { makeFolder, renameOver, writeAtomically } from "./files.js";
|
|
9
9
|
export { commentWithPlan, currentPlan, planOf, withoutPlans } from "./plans.js";
|
|
10
|
+
export { forgetPositionShown, hostProcess, notePositionShown, positionShownNote } from "./position-shown.js";
|
|
10
11
|
export { endTableTalk, loadTableTalk, saveTableTalk, tableTalkStamp } from "./table-talk.js";
|
|
11
12
|
export { heardLines, talkLimits, talkLine, unheardWords, wakesTheChat, WordsRefused } from "./talk.js";
|
|
12
13
|
export { keepThought, notePondering, ponderingOn, ponderingUntil, thoughtForLastMove, thoughtKey, waitingSince } from "./thoughts.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The position shown to a chat by its session, as the host names it to its hooks, and by the host's process, when it names one: the game by its start, none
|
|
3
|
+
* when there is none, its plies, the stamp of game.json after the move, when, and the keys of the player's words given with it.
|
|
4
|
+
*/
|
|
5
|
+
export interface PositionShown {
|
|
6
|
+
readonly session: string;
|
|
7
|
+
readonly process?: string;
|
|
8
|
+
readonly stamp?: string;
|
|
9
|
+
readonly started?: string;
|
|
10
|
+
readonly plies: number;
|
|
11
|
+
readonly at: string;
|
|
12
|
+
readonly given: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The Claude Code process this one runs in, as it names it to its hooks and to the MCP server alike, the same through /clear and /resume: none when it
|
|
16
|
+
* names none. Not documented: Priya measured it in Claude Code 2.1.323.
|
|
17
|
+
*/
|
|
18
|
+
export declare function hostProcess(): string | undefined;
|
|
19
|
+
/** Notes the position shown to a chat, over the last one. */
|
|
20
|
+
export declare function notePositionShown(folder: string, shown: PositionShown): void;
|
|
21
|
+
/** The position a hook showed a chat of the folder, or undefined when there is no note, or it cannot be read. */
|
|
22
|
+
export declare function positionShownNote(folder: string): PositionShown | undefined;
|
|
23
|
+
/** Forgets the note; one that cannot go now is taken again, which changes nothing, or dropped once the game has moved on. */
|
|
24
|
+
export declare function forgetPositionShown(folder: string): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The position a hook showed a chat outside its server (D-71), and the player's words on the board page it gave with it: `chesspal prompt` notes them
|
|
2
|
+
// after playing the move the player wrote alone, and the chat's server takes them before its next tool, so that the AI moves without asking for status
|
|
3
|
+
// and no tool gives those words again; the chat's doorbell does not ring for that move, which the chat hears from the prompt itself.
|
|
4
|
+
import { readFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { writeAtomically } from "./files.js";
|
|
7
|
+
/**
|
|
8
|
+
* The Claude Code process this one runs in, as it names it to its hooks and to the MCP server alike, the same through /clear and /resume: none when it
|
|
9
|
+
* names none. Not documented: Priya measured it in Claude Code 2.1.323.
|
|
10
|
+
*/
|
|
11
|
+
export function hostProcess() {
|
|
12
|
+
const socket = process.env.CLAUDE_CODE_MESSAGING_SOCKET?.trim();
|
|
13
|
+
return socket === undefined || socket === "" ? undefined : socket;
|
|
14
|
+
}
|
|
15
|
+
/** Notes the position shown to a chat, over the last one. */
|
|
16
|
+
export function notePositionShown(folder, shown) {
|
|
17
|
+
writeAtomically(join(folder, fileName), `${JSON.stringify(shown)}\n`);
|
|
18
|
+
}
|
|
19
|
+
/** The position a hook showed a chat of the folder, or undefined when there is no note, or it cannot be read. */
|
|
20
|
+
export function positionShownNote(folder) {
|
|
21
|
+
try {
|
|
22
|
+
const shown = JSON.parse(readFileSync(join(folder, fileName), "utf8"));
|
|
23
|
+
if (typeof shown.session !== "string" || typeof shown.at !== "string" || !Number.isInteger(shown.plies)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
// A note of an older Chesspal has no words.
|
|
27
|
+
const given = Array.isArray(shown.given) ? shown.given.filter((key) => typeof key === "string") : [];
|
|
28
|
+
return {
|
|
29
|
+
session: shown.session,
|
|
30
|
+
...(typeof shown.process === "string" ? { process: shown.process } : {}),
|
|
31
|
+
...(typeof shown.stamp === "string" ? { stamp: shown.stamp } : {}),
|
|
32
|
+
...(typeof shown.started === "string" ? { started: shown.started } : {}),
|
|
33
|
+
plies: shown.plies,
|
|
34
|
+
at: shown.at,
|
|
35
|
+
given,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Forgets the note; one that cannot go now is taken again, which changes nothing, or dropped once the game has moved on. */
|
|
43
|
+
export function forgetPositionShown(folder) {
|
|
44
|
+
try {
|
|
45
|
+
rmSync(join(folder, fileName), { force: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Another process holds it for a moment.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const fileName = "position-shown.json";
|
package/dist/home/index.d.ts
CHANGED
package/dist/home/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Where the player's data is (D-71): one folder for every chat, apart from the host's folder. Below the commands, beside the games and the profiles.
|
|
2
2
|
export { dataFolder, tableFolder } from "./data-folder.js";
|
|
3
3
|
export { openFolder } from "./open-folder.js";
|
|
4
|
-
export { samePath } from "./same-path.js";
|
|
4
|
+
export { realPath, samePath } from "./same-path.js";
|
package/dist/home/same-path.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
/** Whether two paths name the same folder: without regard to case on Windows. */
|
|
1
|
+
/** Whether two paths name the same folder: as written, or through the links and junctions on their way; without regard to case on Windows. */
|
|
2
2
|
export declare function samePath(one: string, other: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* A path with the links and junctions on its way followed, as a page's ports are counted from it: two names of one folder give one path. Case, drive
|
|
5
|
+
* letters and short names stay as written, so a path with no link stays as it was. A folder not made yet keeps the rest of its path as written.
|
|
6
|
+
*/
|
|
7
|
+
export declare function realPath(path: string): string;
|
package/dist/home/same-path.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
/** Whether two paths name the same folder: as written, or through the links and junctions on their way; without regard to case on Windows. */
|
|
3
4
|
export function samePath(one, other) {
|
|
4
|
-
|
|
5
|
+
// The same path as written needs no disk: only two different names may be one folder.
|
|
6
|
+
return sameText(resolve(one), resolve(other)) || sameText(realPath(one), realPath(other));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A path with the links and junctions on its way followed, as a page's ports are counted from it: two names of one folder give one path. Case, drive
|
|
10
|
+
* letters and short names stay as written, so a path with no link stays as it was. A folder not made yet keeps the rest of its path as written.
|
|
11
|
+
*/
|
|
12
|
+
export function realPath(path) {
|
|
13
|
+
const written = resolve(path);
|
|
14
|
+
try {
|
|
15
|
+
return realpathSync(written);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
const parent = dirname(written);
|
|
19
|
+
return parent === written ? written : join(realPath(parent), basename(written));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function sameText(one, other) {
|
|
23
|
+
return process.platform === "win32" ? one.toLowerCase() === other.toLowerCase() : one === other;
|
|
5
24
|
}
|
|
@@ -11,6 +11,11 @@ export declare function setUpHosts(folder: string, platform: string): string[];
|
|
|
11
11
|
export declare function outdatedHostFiles(folder: string, platform: string): string[];
|
|
12
12
|
/** Where Codex reads a folder's hooks, which it runs only once the player trusts them. */
|
|
13
13
|
export declare function codexHooksPath(folder: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the host runs the doorbell's hook init writes, Claude Code and Codex by the names they give the server, so the player's act on the page wakes
|
|
16
|
+
* the chat; the command line, which names no host, is taken as one. Elsewhere the player tells the chat in its window.
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasDoorbell(host: string | undefined): boolean;
|
|
14
19
|
/** The instructions of the AI opponent, read from the package wherever it is installed: the MCP server's `instructions` tool gives them (D-102). */
|
|
15
20
|
export declare function opponentInstructions(): string;
|
|
16
21
|
/** The lines that start a chat, in the MCP server's instructions and in AGENTS.md: call `instructions`, then `status` (D-102). */
|