chesspal 0.3.2 → 0.3.4
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 +49 -10
- package/dist/commands/analysis/analyse.d.ts +4 -1
- package/dist/commands/analysis/analyse.js +46 -12
- package/dist/commands/analysis/index.d.ts +1 -0
- package/dist/commands/analysis/index.js +2 -1
- package/dist/commands/analysis/site-games.d.ts +27 -0
- package/dist/commands/analysis/site-games.js +84 -0
- package/dist/commands/answers/agents.d.ts +1 -1
- package/dist/commands/answers/agents.js +8 -4
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +3 -2
- package/dist/commands/profiles/index.d.ts +1 -1
- package/dist/commands/profiles/index.js +1 -1
- package/dist/commands/profiles/profiles.d.ts +3 -1
- package/dist/commands/profiles/profiles.js +2 -1
- package/dist/commands/registry/context.d.ts +4 -1
- package/dist/commands/registry/registry.d.ts +2 -0
- package/dist/core/accounts.d.ts +11 -0
- package/dist/core/accounts.js +14 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/page/app/app.js +37 -13
- package/dist/page/app/served.js +8 -3
- package/dist/page/dialogs/analyses.js +3 -13
- package/dist/page/dialogs/dialogs.css +102 -0
- package/dist/page/dialogs/outcome.js +13 -0
- package/dist/page/dialogs/site-games.js +71 -0
- package/dist/page/dialogs/studied.js +3 -14
- package/dist/page/dialogs/study.js +30 -13
- package/dist/page/game/column.js +1 -1
- package/dist/page/game/game.css +25 -2
- package/dist/page/guide/en.md +1 -0
- package/dist/page/guide/it.md +1 -0
- package/dist/page/helps/helps.css +100 -23
- package/dist/page/helps/helps.js +7 -7
- package/dist/page/table/window.js +6 -5
- package/dist/page/talk/talk.js +2 -2
- package/dist/page/text.js +44 -4
- package/dist/server/bell/bell.js +14 -1
- package/dist/server/mcp/mcp.js +2 -2
- package/dist/server/page/analyses.d.ts +5 -0
- package/dist/server/page/analyses.js +18 -1
- package/dist/server/page/attach.js +8 -3
- package/dist/server/page/game.js +8 -5
- package/dist/server/page/page.js +17 -2
- package/dist/sites/chesscom.d.ts +1 -1
- package/dist/sites/chesscom.js +1 -1
- package/dist/sites/games/chesscom.d.ts +4 -0
- package/dist/sites/games/chesscom.js +54 -0
- package/dist/sites/games/game.d.ts +34 -0
- package/dist/sites/games/game.js +64 -0
- package/dist/sites/games/games.d.ts +14 -0
- package/dist/sites/games/games.js +22 -0
- package/dist/sites/games/index.d.ts +2 -0
- package/dist/sites/games/index.js +3 -0
- package/dist/sites/games/lichess.d.ts +4 -0
- package/dist/sites/games/lichess.js +45 -0
- package/dist/sites/index.d.ts +2 -1
- package/dist/sites/index.js +5 -3
- package/dist/sites/lichess.d.ts +1 -1
- package/dist/sites/lichess.js +1 -1
- package/dist/sites/request/index.d.ts +1 -0
- package/dist/sites/request/index.js +2 -0
- package/dist/sites/{request.d.ts → request/request.d.ts} +7 -0
- package/dist/sites/request/request.js +37 -0
- package/dist/sites/sites.d.ts +1 -1
- package/dist/sites/sites.js +1 -1
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
- package/dist/sites/request.js +0 -15
|
@@ -238,12 +238,19 @@
|
|
|
238
238
|
padding: 3px 6px;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/* The coach's words at the top of the card (D-88)
|
|
241
|
+
/* The coach's words at the top of the card (D-88), tinted rows as the tactics': each judgment in its badge's colour, the hint in the accent behind a bulb */
|
|
242
242
|
.helps-card .coach {
|
|
243
243
|
display: flex;
|
|
244
244
|
flex-direction: column;
|
|
245
|
-
gap:
|
|
246
|
-
font-size:
|
|
245
|
+
gap: 3px;
|
|
246
|
+
font-size: 13px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.helps-card .coach .judged,
|
|
250
|
+
.helps-card .coach .coach-hint {
|
|
251
|
+
--row: var(--accent);
|
|
252
|
+
border-radius: 6px;
|
|
253
|
+
background: color-mix(in srgb, var(--row) 10%, transparent);
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
.coach-hint summary {
|
|
@@ -257,6 +264,48 @@
|
|
|
257
264
|
display: none;
|
|
258
265
|
}
|
|
259
266
|
|
|
267
|
+
/* A row's head: its icon, its words in ink, the sign of a line that opens on the right */
|
|
268
|
+
.helps-card .coach .head,
|
|
269
|
+
.helps-card .coach summary {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 6px;
|
|
273
|
+
padding: 3px 8px 3px 6px;
|
|
274
|
+
border-radius: 6px;
|
|
275
|
+
color: var(--ink);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.helps-card .coach summary {
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
list-style: none;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.helps-card .coach summary:hover,
|
|
284
|
+
.helps-card .coach details[open] > summary {
|
|
285
|
+
background: color-mix(in srgb, var(--row) 16%, transparent);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.helps-card .coach .row-icon {
|
|
289
|
+
display: grid;
|
|
290
|
+
place-items: center;
|
|
291
|
+
flex: none;
|
|
292
|
+
width: 16px;
|
|
293
|
+
height: 16px;
|
|
294
|
+
color: var(--row);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.board-map .coach-hint .row-icon {
|
|
298
|
+
display: none;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* A finger needs a taller line (Zoe) */
|
|
302
|
+
@media (pointer: coarse) {
|
|
303
|
+
.helps-card .coach .head,
|
|
304
|
+
.helps-card .coach summary {
|
|
305
|
+
padding-block: 7px;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
260
309
|
/* The same sign on both, so that on a phone, with no pointer, they read as lines that open (Zoe) */
|
|
261
310
|
.helps-card .coach summary::after,
|
|
262
311
|
.board-map .coach-hint summary::after {
|
|
@@ -265,12 +314,31 @@
|
|
|
265
314
|
color: var(--soft);
|
|
266
315
|
}
|
|
267
316
|
|
|
317
|
+
.helps-card .coach summary::after {
|
|
318
|
+
margin-left: auto;
|
|
319
|
+
}
|
|
320
|
+
|
|
268
321
|
.helps-card .coach details[open] > summary::after,
|
|
269
322
|
.board-map .coach-hint[open] > summary::after {
|
|
270
323
|
content: "▾";
|
|
271
324
|
}
|
|
272
325
|
|
|
273
|
-
|
|
326
|
+
/* The words opened, under the head's text */
|
|
327
|
+
.helps-card .coach .note {
|
|
328
|
+
display: block;
|
|
329
|
+
margin: 0;
|
|
330
|
+
padding: 2px 8px 6px 28px;
|
|
331
|
+
color: var(--say-ink);
|
|
332
|
+
font-size: 13px;
|
|
333
|
+
line-height: 1.4;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.helps-card .coach .legend {
|
|
337
|
+
margin: 0;
|
|
338
|
+
padding: 0 8px 6px 28px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.board-map .coach-hint .note {
|
|
274
342
|
display: block;
|
|
275
343
|
margin-top: 4px;
|
|
276
344
|
color: var(--say-ink);
|
|
@@ -472,7 +540,8 @@
|
|
|
472
540
|
color: var(--row);
|
|
473
541
|
}
|
|
474
542
|
|
|
475
|
-
.help-rows .row-icon svg
|
|
543
|
+
.help-rows .row-icon svg,
|
|
544
|
+
.coach-hint .row-icon svg {
|
|
476
545
|
width: 14px;
|
|
477
546
|
height: 14px;
|
|
478
547
|
fill: none;
|
|
@@ -553,41 +622,49 @@
|
|
|
553
622
|
gap: 10px;
|
|
554
623
|
}
|
|
555
624
|
|
|
556
|
-
/* The three phases in a row,
|
|
625
|
+
/* The three phases in a row of arrows, each set in the next: the current one full in the accent, those gone by in its tint, those to come unlit */
|
|
557
626
|
.phases {
|
|
558
627
|
display: flex;
|
|
559
|
-
align-items: center;
|
|
560
628
|
margin: 0;
|
|
561
629
|
padding: 0;
|
|
562
630
|
list-style: none;
|
|
563
631
|
font-size: 12px;
|
|
632
|
+
line-height: 22px;
|
|
633
|
+
text-align: center;
|
|
564
634
|
color: var(--soft);
|
|
565
635
|
}
|
|
566
636
|
|
|
637
|
+
.phases li {
|
|
638
|
+
flex: 1;
|
|
639
|
+
padding: 0 6px 0 12px;
|
|
640
|
+
background: color-mix(in srgb, var(--soft) 14%, transparent);
|
|
641
|
+
white-space: nowrap;
|
|
642
|
+
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%, 8px 50%);
|
|
643
|
+
}
|
|
644
|
+
|
|
567
645
|
.phases li + li {
|
|
568
|
-
|
|
569
|
-
display: flex;
|
|
570
|
-
align-items: center;
|
|
571
|
-
gap: 6px;
|
|
572
|
-
margin-left: 6px;
|
|
646
|
+
margin-left: -5px;
|
|
573
647
|
}
|
|
574
648
|
|
|
575
|
-
.phases li
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
background: var(--line);
|
|
649
|
+
.phases li:first-child {
|
|
650
|
+
padding-left: 8px;
|
|
651
|
+
border-radius: 5px 0 0 5px;
|
|
652
|
+
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%);
|
|
580
653
|
}
|
|
581
654
|
|
|
582
|
-
.phases li
|
|
583
|
-
|
|
584
|
-
|
|
655
|
+
.phases li:last-child {
|
|
656
|
+
border-radius: 0 5px 5px 0;
|
|
657
|
+
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 8px 50%);
|
|
585
658
|
}
|
|
586
659
|
|
|
587
|
-
.phases li
|
|
588
|
-
|
|
589
|
-
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
|
660
|
+
.phases li.done {
|
|
661
|
+
background: color-mix(in srgb, var(--accent) 24%, transparent);
|
|
590
662
|
color: var(--ink);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.phases li[aria-current="step"] {
|
|
666
|
+
background: var(--accent);
|
|
667
|
+
color: var(--on-accent);
|
|
591
668
|
font-weight: 700;
|
|
592
669
|
}
|
|
593
670
|
|
package/dist/page/helps/helps.js
CHANGED
|
@@ -41,12 +41,12 @@ export function BareButton({ bare, onBare, text }) {
|
|
|
41
41
|
return (_jsx(HoldButton, { on: bare, onHold: onBare, title: text.helpsCard.bareHint, label: text.helpsCard.bare, kind: "bare-button", children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("path", { d: "M1.5 8S4 3.5 8 3.5 14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8Z" }), _jsx("circle", { cx: "8", cy: "8", r: "2" }), _jsx("path", { d: "M2.5 2.5l11 11" })] }) }));
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* The coach's hint, a line that opens on its words and the legend of what it draws (D-88): in the helps' card
|
|
45
|
-
* map, where its drawings are in sight (Zoe).
|
|
44
|
+
* The coach's hint, a line that opens on its words and the legend of what it draws (D-88): in the helps' card a tinted row behind a bulb, as the tactics',
|
|
45
|
+
* and on a phone under the board, beside the map, where its drawings are in sight (Zoe).
|
|
46
46
|
*/
|
|
47
47
|
export function HintLine({ hint, open, onOpen, text }) {
|
|
48
48
|
const words = text.helpsCard;
|
|
49
|
-
return (_jsxs("details", { class: "coach-hint", open: open, onToggle: (event) => onOpen(event.currentTarget.open), children: [
|
|
49
|
+
return (_jsxs("details", { class: "coach-hint", open: open, onToggle: (event) => onOpen(event.currentTarget.open), children: [_jsxs("summary", { children: [_jsxs(RowIcon, { children: [_jsx("path", { d: "M5.5 10.5C4.3 9.7 3.5 8.4 3.5 7a4.5 4.5 0 0 1 9 0c0 1.4-.8 2.7-2 3.5V12h-5Z" }), _jsx("path", { d: "M6.5 14.2h3" })] }), open ? words.hideHint : words.showHint] }), _jsx("span", { class: "note", children: _jsx(Marked, { text: hint.text }) }), hint.arrows === undefined && hint.squares === undefined ? null : _jsx(Legend, { review: hint, words: words.drawn })] }));
|
|
50
50
|
}
|
|
51
51
|
/** An icon the mouse holds, while what it shows lasts; a finger, or the keyboard, turns it on and off (D-78). */
|
|
52
52
|
function HoldButton({ on, onHold, title, label, kind, children }) {
|
|
@@ -54,11 +54,11 @@ function HoldButton({ on, onHold, title, label, kind, children }) {
|
|
|
54
54
|
const mouse = (event) => event instanceof PointerEvent && event.pointerType === "mouse";
|
|
55
55
|
return (_jsx("button", { type: "button", class: kind === undefined ? "hold-button" : `hold-button ${kind}`, "aria-pressed": on, "aria-label": label, title: title, onPointerDown: (event) => mouse(event) && onHold(true), onPointerUp: (event) => mouse(event) && onHold(false), onPointerLeave: (event) => mouse(event) && onHold(false), onClick: (event) => !mouse(event) && onHold(!on), children: children }));
|
|
56
56
|
}
|
|
57
|
-
/** The coach's judgment of the player's move: its badge and the move in a
|
|
57
|
+
/** The coach's judgment of the player's move: its badge and the move in a row tinted in the badge's colour, which opens on its note (D-78, D-88). */
|
|
58
58
|
function Judged({ ply, text }) {
|
|
59
59
|
const { badge, note } = ply.review ?? {};
|
|
60
60
|
const head = (_jsxs(_Fragment, { children: [badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: badge }), _jsxs("b", { children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] }), " ", badge === undefined ? null : text.badges[badge]] }));
|
|
61
|
-
return (_jsx("div", { class: `move-note judged ${badge ?? ""}`, children: note === undefined ? _jsx("span", { class: "head", children: head }) : (_jsxs("details", { children: [_jsx("summary", { class: "head", children: head }), _jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })] })) }));
|
|
61
|
+
return (_jsx("div", { class: `move-note judged ${badge ?? ""}`, style: badge === undefined ? undefined : { "--row": `var(--badge-${badge})` }, children: note === undefined ? _jsx("span", { class: "head", children: head }) : (_jsxs("details", { children: [_jsx("summary", { class: "head", children: head }), _jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })] })) }));
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* The tactics in lines (D-91): one a kind of fact and a side, its count, red when it threatens the player and green when it is their chance; a click opens
|
|
@@ -184,9 +184,9 @@ function squaresIn(line) {
|
|
|
184
184
|
}
|
|
185
185
|
return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
|
|
186
186
|
}
|
|
187
|
-
/** The game's three phases in a row
|
|
187
|
+
/** The game's three phases in a row of arrows: the current one full, those gone by lit, those to come not. */
|
|
188
188
|
function Phases({ current, words }) {
|
|
189
|
-
return (_jsx("ol", { class: "phases", children: phases.map((phase) => (_jsx("li", { "aria-current": phase === current ? "step" : undefined, children:
|
|
189
|
+
return (_jsx("ol", { class: "phases", children: phases.map((phase, at) => (_jsx("li", { class: at < phases.indexOf(current) ? "done" : undefined, "aria-current": phase === current ? "step" : undefined, children: words.phases[phase] }, phase))) }));
|
|
190
190
|
}
|
|
191
191
|
/** The squares only one side controls and those both do, in a bar as long as their counts, the player's side on the left; each part's title says it in words. */
|
|
192
192
|
function ControlBar({ controlled, sides, you, words }) {
|
|
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from "preact/hooks";
|
|
3
3
|
import { Dialog } from "../dialogs/dialog.js";
|
|
4
4
|
import { checkIcon } from "../dialogs/profiles.js";
|
|
5
|
-
/** The chat's window: closed once a chat opens or starts again, the page then saying where it stands. */
|
|
6
|
-
export function ChatDialog({ open, table, settings, onChange, name, onClose, text }) {
|
|
5
|
+
/** The chat's window: closed once a chat opens or starts again, the page then saying where it stands; `review` when it opened to have a game analysed. */
|
|
6
|
+
export function ChatDialog({ open, review = false, table, settings, onChange, name, onClose, text }) {
|
|
7
7
|
const [sending, setSending] = useState(false);
|
|
8
8
|
const [failed, setFailed] = useState(false);
|
|
9
9
|
useEffect(() => setFailed(false), [open]);
|
|
@@ -25,20 +25,21 @@ export function ChatDialog({ open, table, settings, onChange, name, onClose, tex
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
// While a chat opens or waits to start again, the rows wait too, and the note says why.
|
|
28
|
+
const chooseNote = review ? words.reviewChoose : words.choose;
|
|
28
29
|
const note = installed.length === 0
|
|
29
30
|
? words.install(name)
|
|
30
31
|
: busy
|
|
31
|
-
? chatState(table, name, text) ??
|
|
32
|
+
? chatState(table, name, text) ?? chooseNote
|
|
32
33
|
: byHand
|
|
33
34
|
? words.byHand(name)
|
|
34
35
|
: table.chat
|
|
35
36
|
? words.restartHint
|
|
36
|
-
:
|
|
37
|
+
: chooseNote;
|
|
37
38
|
const offersChoices = installed.some((host) => host.models.length > 0 || host.efforts.length > 0);
|
|
38
39
|
// The chat at the table started with other choices than the ones kept now: its row starts it again with these, else it only closes the window.
|
|
39
40
|
const started = table.held ? table.started : undefined;
|
|
40
41
|
const changed = (host) => started !== undefined && (chosen(host, "model", settings) !== (started.model ?? "") || chosen(host, "effort", settings) !== (started.effort ?? ""));
|
|
41
|
-
return (_jsxs(Dialog, { open: open, title: words.title, onClose: onClose, text: text, children: [_jsx("p", { class: "note", children: note }), _jsxs("div", { class: "chat-hosts", role: "group", "aria-label": words.title, children: [offersChoices
|
|
42
|
+
return (_jsxs(Dialog, { open: open, title: review ? words.reviewTitle : words.title, onClose: onClose, text: text, children: [_jsx("p", { class: "note", children: note }), _jsxs("div", { class: "chat-hosts", role: "group", "aria-label": review ? words.reviewTitle : words.title, children: [offersChoices
|
|
42
43
|
? (_jsxs("div", { class: "chat-head", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: words.model }), _jsx("span", { children: words.effort })] }))
|
|
43
44
|
: null, installed.map((host) => {
|
|
44
45
|
const atTable = table.chat && table.host === host.name;
|
package/dist/page/talk/talk.js
CHANGED
|
@@ -19,7 +19,7 @@ export function AiWords({ words, newestFirst, voice }) {
|
|
|
19
19
|
* The AI's word on the move shown of a game in analysis (D-69), in a bubble under the Torretta beside the AI's name, pointing up at it: the move, its badge by name,
|
|
20
20
|
* and its note when it wrote one; read out as the player steps through the game. The Torretta and the name stay when the move has no word.
|
|
21
21
|
*/
|
|
22
|
-
export function AiNote({ ply, opening, closing, summary, reviewing, name, by, mood, voice, text }) {
|
|
22
|
+
export function AiNote({ ply, opening, closing, summary, reviewing, onComplete, name, by, mood, voice, text }) {
|
|
23
23
|
// On a move of the book without the AI's note, the AI reports what the book says: the opening in the title, the variation under it.
|
|
24
24
|
const book = ply?.review?.note === undefined ? opening : undefined;
|
|
25
25
|
const shown = ply?.review ?? (book === undefined ? undefined : { badge: "book" });
|
|
@@ -35,7 +35,7 @@ export function AiNote({ ply, opening, closing, summary, reviewing, name, by, mo
|
|
|
35
35
|
// The Torretta stays in sight through the whole analysis, over its bubbles, which show only where it wrote something.
|
|
36
36
|
return (_jsxs("div", { class: "ai-note", "aria-live": "polite", children: [_jsxs("p", { class: "who", children: [_jsx(Torretta, { size: 36, mood: mood }), _jsxs("span", { class: "signed", children: [name, by === undefined ? null : _jsx("small", { children: signature(by.annotator, by.effort) })] })] }), ply === undefined || shown === undefined ? null : (_jsxs("p", { class: `say move-note ${shown.badge ?? ""}`, children: [_jsxs("span", { class: "head", children: [shown.badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: shown.badge }), _jsxs("b", { children: [_jsxs("span", { class: "visually-hidden", children: [ply.number, ply.side === "white" ? "." : "...", ply.san] }), _jsxs("span", { "aria-hidden": "true", children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] })] }), " ", title] }), note === undefined ? null : (_jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })), shown.arrows === undefined && shown.squares === undefined ? null : _jsx(Legend, { review: shown, words: text.drawn })] })), summary === undefined ? null : _jsx(GameReview, { by: name, summary: summary, text: text }), closing === undefined ? null : (_jsx("p", { class: "say closing", children: _jsx(Marked, { text: closing }) })), reviewing === true
|
|
37
37
|
? (_jsxs("p", { class: "say reviewing", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(name)] }))
|
|
38
|
-
: null] }));
|
|
38
|
+
: null, onComplete === undefined ? null : (_jsxs("p", { class: "say incomplete", children: [_jsx("span", { role: "status", children: text.incomplete }), _jsx("button", { type: "button", title: text.completeHint, "aria-label": text.completeHint, onClick: onComplete, children: text.complete })] }))] }));
|
|
39
39
|
}
|
|
40
40
|
/** The squares and moves in the AI's words, such as e3, Cxe4, 7.Nd4 or O-O: in bold, as the player looks for them on the board. */
|
|
41
41
|
export function Marked({ text }) {
|
package/dist/page/text.js
CHANGED
|
@@ -87,7 +87,7 @@ const english = {
|
|
|
87
87
|
saveMoves: "PGN",
|
|
88
88
|
saveMovesHint: "The moves alone, to open on Lichess or chess.com",
|
|
89
89
|
noGameNow: "No game in progress",
|
|
90
|
-
analysing: "Analysis
|
|
90
|
+
analysing: "Analysis",
|
|
91
91
|
badges: {
|
|
92
92
|
brilliant: "Brilliant",
|
|
93
93
|
great: "Great move",
|
|
@@ -115,6 +115,9 @@ const english = {
|
|
|
115
115
|
mated: (side) => `${side === "white" ? "White" : "Black"} is mated`,
|
|
116
116
|
},
|
|
117
117
|
reviewing: (name) => `${name} is going over the game…`,
|
|
118
|
+
incomplete: "Analysis not complete",
|
|
119
|
+
complete: "Complete it",
|
|
120
|
+
completeHint: "Complete the analysis",
|
|
118
121
|
you: "You",
|
|
119
122
|
ai: "AI",
|
|
120
123
|
sides: { white: "White", black: "Black" },
|
|
@@ -170,6 +173,8 @@ const english = {
|
|
|
170
173
|
table: {
|
|
171
174
|
title: "Who are you playing?",
|
|
172
175
|
choose: "Choose the chat to play with: it opens in a window of its own, to keep open.",
|
|
176
|
+
reviewTitle: "Who reviews the game?",
|
|
177
|
+
reviewChoose: "Choose the chat that reviews the game: it opens in a window of its own, to keep open.",
|
|
173
178
|
restartHint: "Choose the model and effort, then press Claude Code or Codex: the chat starts again, clean, in the same window, once it has ended its answer. The game stays as it is.",
|
|
174
179
|
byHand: (name) => `You opened this chat yourself: change ${name}'s model with /model in its window, or close it and open one from here, with the choices below.`,
|
|
175
180
|
noChat: "No chat at the table: choose one",
|
|
@@ -298,7 +303,7 @@ const english = {
|
|
|
298
303
|
start: "Analyse",
|
|
299
304
|
yours: "Your analyses",
|
|
300
305
|
bring: "Load a game",
|
|
301
|
-
bringNote: "
|
|
306
|
+
bringNote: "from Lichess or Chess.com, or a PGN pasted or from a file",
|
|
302
307
|
search: "Names, models, openings…",
|
|
303
308
|
noMatch: "No game found",
|
|
304
309
|
reviewed: "reviewed",
|
|
@@ -319,6 +324,19 @@ const english = {
|
|
|
319
324
|
askUnreview: (game) => ({ question: `Delete the analysis of ${game}?`, note: "Its badges, notes and the coach's judgments go; the game stays.", yes: "Delete" }),
|
|
320
325
|
askRemove: (game, when) => ({ question: `Delete the game ${game}${when === undefined ? "" : ` of ${when}`}?`, note: "With its analysis. It cannot be undone.", yes: "Delete" }),
|
|
321
326
|
cancel: "Cancel",
|
|
327
|
+
siteGames: "Your latest games on the sites",
|
|
328
|
+
siteGamesLoading: "Asking Lichess and Chess.com…",
|
|
329
|
+
siteGamesNone: "No finished game on the sites.",
|
|
330
|
+
siteGamesNoAccount: "Your names on Lichess and Chess.com bring your latest games here.",
|
|
331
|
+
siteGamesProfile: "Add them",
|
|
332
|
+
bothUnreached: "Lichess and Chess.com aren't answering: we'll try again later",
|
|
333
|
+
siteGamesRefused: {
|
|
334
|
+
"game-going": "A game is going on: finish it to bring another.",
|
|
335
|
+
"no-accounts": "Your profile names no account on Lichess or Chess.com.",
|
|
336
|
+
"no-network": "Chesspal cannot reach the sites from here.",
|
|
337
|
+
},
|
|
338
|
+
playedAs: { white: "You played White", black: "You played Black" },
|
|
339
|
+
speed: (name) => ({ ultraBullet: "ultrabullet", correspondence: "correspondence", daily: "daily" }[name] ?? readableSpeed(name)),
|
|
322
340
|
download: "Annotated PGN",
|
|
323
341
|
downloadHint: "The game with the review's comments and judgments",
|
|
324
342
|
analyseIt: "Analyse the game",
|
|
@@ -494,7 +512,7 @@ const italian = {
|
|
|
494
512
|
saveMoves: "PGN",
|
|
495
513
|
saveMovesHint: "Solo le mosse, da aprire su Lichess o chess.com",
|
|
496
514
|
noGameNow: "Nessuna partita in corso",
|
|
497
|
-
analysing: "Analisi
|
|
515
|
+
analysing: "Analisi",
|
|
498
516
|
badges: {
|
|
499
517
|
brilliant: "Geniale",
|
|
500
518
|
great: "Ottima mossa",
|
|
@@ -522,6 +540,9 @@ const italian = {
|
|
|
522
540
|
mated: (side) => `Matto al ${side === "white" ? "Bianco" : "Nero"}`,
|
|
523
541
|
},
|
|
524
542
|
reviewing: (name) => `${name} sta analizzando la partita…`,
|
|
543
|
+
incomplete: "Analisi non completa",
|
|
544
|
+
complete: "Completa",
|
|
545
|
+
completeHint: "Completa l'analisi",
|
|
525
546
|
you: "Tu",
|
|
526
547
|
ai: "AI",
|
|
527
548
|
sides: { white: "Bianco", black: "Nero" },
|
|
@@ -577,6 +598,8 @@ const italian = {
|
|
|
577
598
|
table: {
|
|
578
599
|
title: "Contro chi giochi?",
|
|
579
600
|
choose: "Scegli la chat con cui giocare: parte in una finestra a parte, da tenere aperta.",
|
|
601
|
+
reviewTitle: "Chi analizza la partita?",
|
|
602
|
+
reviewChoose: "Scegli la chat che analizza la partita: parte in una finestra a parte, da tenere aperta.",
|
|
580
603
|
restartHint: "Scegli modello e ragionamento, poi premi Claude Code o Codex: la chat riparte pulita nella stessa finestra, appena ha finito di rispondere. La partita resta com'è.",
|
|
581
604
|
byHand: (name) => `Questa chat l'hai aperta tu: cambia il modello di ${name} con /model nella sua finestra, oppure chiudila e aprine una da qui, con le scelte qui sotto.`,
|
|
582
605
|
noChat: "Nessuna chat al tavolo: scegline una",
|
|
@@ -706,7 +729,7 @@ const italian = {
|
|
|
706
729
|
start: "Analizza",
|
|
707
730
|
yours: "Le tue analisi",
|
|
708
731
|
bring: "Carica una partita",
|
|
709
|
-
bringNote: "
|
|
732
|
+
bringNote: "da Lichess o Chess.com, o un PGN incollato o da file",
|
|
710
733
|
search: "Nomi, modelli, aperture…",
|
|
711
734
|
noMatch: "Nessuna partita trovata",
|
|
712
735
|
reviewed: "commentata",
|
|
@@ -727,6 +750,19 @@ const italian = {
|
|
|
727
750
|
askUnreview: (game) => ({ question: `Eliminare l'analisi di ${game}?`, note: "Bollini, note e giudizi dell'allenatore si cancellano; la partita resta.", yes: "Elimina" }),
|
|
728
751
|
askRemove: (game, when) => ({ question: `Eliminare la partita ${game}${when === undefined ? "" : ` del ${when}`}?`, note: "Con la sua analisi. Non si può annullare.", yes: "Elimina" }),
|
|
729
752
|
cancel: "Annulla",
|
|
753
|
+
siteGames: "Le tue ultime partite sui siti",
|
|
754
|
+
siteGamesLoading: "Chiedo a Lichess e Chess.com…",
|
|
755
|
+
siteGamesNone: "Nessuna partita finita sui siti.",
|
|
756
|
+
siteGamesNoAccount: "I tuoi nomi su Lichess e Chess.com portano qui le tue ultime partite.",
|
|
757
|
+
siteGamesProfile: "Aggiungili",
|
|
758
|
+
bothUnreached: "Lichess e Chess.com non rispondono: riproviamo più tardi",
|
|
759
|
+
siteGamesRefused: {
|
|
760
|
+
"game-going": "C'è una partita in corso: finiscila per portarne un'altra.",
|
|
761
|
+
"no-accounts": "Il tuo profilo non ha nomi su Lichess o Chess.com.",
|
|
762
|
+
"no-network": "Da qui Chesspal non raggiunge i siti.",
|
|
763
|
+
},
|
|
764
|
+
playedAs: { white: "Hai giocato col Bianco", black: "Hai giocato col Nero" },
|
|
765
|
+
speed: (name) => ({ ultraBullet: "ultrabullet", classical: "classica", correspondence: "per corrispondenza", daily: "per corrispondenza" }[name] ?? readableSpeed(name)),
|
|
730
766
|
download: "PGN annotato",
|
|
731
767
|
downloadHint: "La partita con i commenti e i giudizi della revisione",
|
|
732
768
|
analyseIt: "Analizza la partita",
|
|
@@ -911,3 +947,7 @@ function moveInWords(words, move) {
|
|
|
911
947
|
function capital(words) {
|
|
912
948
|
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
913
949
|
}
|
|
950
|
+
/** A speed the page does not know, as a word: ultraBullet as «ultra bullet». */
|
|
951
|
+
function readableSpeed(name) {
|
|
952
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
|
|
953
|
+
}
|
package/dist/server/bell/bell.js
CHANGED
|
@@ -6,6 +6,7 @@ import { get } from "node:http";
|
|
|
6
6
|
import { join, resolve } from "node:path";
|
|
7
7
|
import { writeAtomically } from "../../games/index.js";
|
|
8
8
|
import { dataFolder, tableFolder } from "../../home/index.js";
|
|
9
|
+
import { analystAgent } from "../../host-files/index.js";
|
|
9
10
|
import { version } from "../../version.js";
|
|
10
11
|
/** What the chat hears from the doorbell: one line for whatever the player did, never what, only where to read it. */
|
|
11
12
|
export const bellLine = "The player acted on the board page: call status to see it.";
|
|
@@ -171,7 +172,8 @@ export async function bellHeard(input, ports) {
|
|
|
171
172
|
const pages = ours.length > 1 ? `${ours.length} pages of it, none of this chat's session, ${open} open in a browser` : "no page of it";
|
|
172
173
|
return { folder, rang: false, why: `${pages} at ports ${tried.join(", ")}${others.length > 0 ? `; ${others.join(", ")}` : ""}` };
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
+
// The page lets the analyst go as the hook starts waiting, when none runs any more.
|
|
176
|
+
const said = await body(port, hook.analystGone === true ? "/api/bell?analyst=gone" : "/api/bell");
|
|
175
177
|
return said === "ring" ? { folder, rang: true } : { folder, rang: false, why: said === undefined ? `the page at port ${port} did not answer` : `the page at port ${port} said "${said}"` };
|
|
176
178
|
}
|
|
177
179
|
// How long a port may take to say whose page it is: one of this computer answers at once.
|
|
@@ -244,12 +246,23 @@ function hookInput(input) {
|
|
|
244
246
|
return {
|
|
245
247
|
...(typeof parsed.session_id === "string" ? { session: parsed.session_id } : {}),
|
|
246
248
|
...(typeof parsed.cwd === "string" ? { cwd: parsed.cwd } : {}),
|
|
249
|
+
...(analystGone(parsed.background_tasks) ? { analystGone: true } : {}),
|
|
247
250
|
};
|
|
248
251
|
}
|
|
249
252
|
catch {
|
|
250
253
|
return {};
|
|
251
254
|
}
|
|
252
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Whether Claude Code's Stop hook says no analyst runs in the background: after it ends, is stopped or fails, the chat takes a turn, and its Stop hook
|
|
258
|
+
* lists the tasks still running (D-109, Priya's test). A host that lists none says nothing.
|
|
259
|
+
*/
|
|
260
|
+
function analystGone(tasks) {
|
|
261
|
+
return Array.isArray(tasks) && !tasks.some((task) => {
|
|
262
|
+
const { agent_type: agent, status } = (task ?? {});
|
|
263
|
+
return agent === analystAgent && status === "running";
|
|
264
|
+
});
|
|
265
|
+
}
|
|
253
266
|
/**
|
|
254
267
|
* The chat's page among those of its folder: the one of its session; else the only one, or the only one a browser has open, where the player plays. Codex
|
|
255
268
|
* gives the page no session, and a chat closed may leave its page behind for a while.
|
package/dist/server/mcp/mcp.js
CHANGED
|
@@ -191,10 +191,10 @@ function track(call) {
|
|
|
191
191
|
void call.finally(() => running.delete(call));
|
|
192
192
|
return call;
|
|
193
193
|
}
|
|
194
|
-
/** Every hint stated, since the spec reads a missing one as the worst case: everything is local,
|
|
194
|
+
/** Every hint stated, since the spec reads a missing one as the worst case: everything is local, but for the tools that ask the sites the player plays on (D-110). */
|
|
195
195
|
function toolConfig(command) {
|
|
196
196
|
const readOnly = command.readOnly === true;
|
|
197
|
-
const annotations = { readOnlyHint: readOnly, destructiveHint: command.destructive === true, idempotentHint: readOnly || command.idempotent === true, openWorldHint:
|
|
197
|
+
const annotations = { readOnlyHint: readOnly, destructiveHint: command.destructive === true, idempotentHint: readOnly || command.idempotent === true, openWorldHint: command.openWorld === true };
|
|
198
198
|
const meta = command.alwaysLoad ? { "anthropic/alwaysLoad": true } : undefined;
|
|
199
199
|
// The SDK writes the draft's address in every schema: it says nothing to an AI, and costs tokens in every tool.
|
|
200
200
|
return { description: command.description, inputSchema: z.looseObject({}).meta({ ...shownInput(command), $schema: undefined }), annotations, _meta: meta };
|
|
@@ -23,3 +23,8 @@ export interface AnalysisItem {
|
|
|
23
23
|
export declare function analysesOf(home: string): AnalysisItem[];
|
|
24
24
|
/** Answers the page's deletions, of a game or of its review alone, done as the command line does them: true when the request was one. */
|
|
25
25
|
export declare function answerAnalyses(request: IncomingMessage, response: ServerResponse, url: URL, served: Served): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Sends the player's latest games on the sites, as site_games gives them with their PGNs (D-110); its refusal as a 409, with the code the page says it by in
|
|
28
|
+
* its own words: a game going on, no name on the sites, no network.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sendSiteGames(response: ServerResponse, served: Served): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bookOpening, deleteGame, deleteReview } from "../../commands/index.js";
|
|
1
|
+
import { bookOpening, deleteGame, deleteReview, siteGames, SiteGamesRefused } from "../../commands/index.js";
|
|
2
2
|
import { aiModelIn, annotatorOf, isReviewed, leadOf, reviewOf } from "../../core/index.js";
|
|
3
3
|
import { archivedGame, archivedGames, gameInAnalysis, gameStemOf, keptGame, keptGames } from "../../games/index.js";
|
|
4
4
|
import { gameFolder } from "../../profiles/index.js";
|
|
@@ -33,6 +33,23 @@ export function answerAnalyses(request, response, url, served) {
|
|
|
33
33
|
});
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Sends the player's latest games on the sites, as site_games gives them with their PGNs (D-110); its refusal as a 409, with the code the page says it by in
|
|
38
|
+
* its own words: a game going on, no name on the sites, no network.
|
|
39
|
+
*/
|
|
40
|
+
export function sendSiteGames(response, served) {
|
|
41
|
+
runRoute(response, async () => {
|
|
42
|
+
try {
|
|
43
|
+
sendJson(response, 200, await siteGames.run({}, served.context));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!(error instanceof SiteGamesRefused)) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
sendJson(response, 409, { error: error.message, refusal: error.refusal });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
36
53
|
const deletions = { "/api/delete-game": deleteGame, "/api/delete-review": deleteReview };
|
|
37
54
|
function itemOf(name, read, taken) {
|
|
38
55
|
let record;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The table's end of the chat attached to it (D-104): the chat attaches and stays while its process lives, says when it calls a tool, sends a copy of what
|
|
2
2
|
// it saw, and asks for the New game window. One chat at a time, of this version alone; a page a chat serves itself has none of these routes.
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { forgetTheAi, noteAiHost, noteAiModel } from "../../games/index.js";
|
|
4
|
+
import { forgetAnalyst, forgetTheAi, noteAiHost, noteAiModel } from "../../games/index.js";
|
|
5
5
|
import { gameFolder } from "../../profiles/index.js";
|
|
6
6
|
import { version } from "../../version.js";
|
|
7
7
|
import { chatAttach, chatCopy } from "./chat.js";
|
|
@@ -67,10 +67,15 @@ function attach(body, response, served) {
|
|
|
67
67
|
});
|
|
68
68
|
tellWatchers(served.watchers);
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The next chat may play in another host, with another model: the AI's card shows its own from its first move (Federico). The analyst ended with its chat
|
|
72
|
+
* (D-109): the game it held is free for the next one.
|
|
73
|
+
*/
|
|
71
74
|
function forgetChat(served) {
|
|
72
75
|
try {
|
|
73
|
-
|
|
76
|
+
const folder = gameFolder(served.context.home());
|
|
77
|
+
forgetTheAi(folder);
|
|
78
|
+
forgetAnalyst(folder);
|
|
74
79
|
}
|
|
75
80
|
catch {
|
|
76
81
|
// A data folder that cannot be read keeps no notes to forget.
|
package/dist/server/page/game.js
CHANGED
|
@@ -245,7 +245,7 @@ async function studyFromPage(request, response, served) {
|
|
|
245
245
|
let brought;
|
|
246
246
|
try {
|
|
247
247
|
// Without what the chat was told: the analyst's instructions stay for the AI's own analyse_game.
|
|
248
|
-
const opened = last ? lastPlayed(served.context) : game === undefined ? kept(served, pgn) : namedGame(served.context, game);
|
|
248
|
+
const opened = last ? lastPlayed(served.context) : game === undefined ? await kept(served, pgn) : namedGame(served.context, game);
|
|
249
249
|
reviewed = isReviewed(opened.record);
|
|
250
250
|
brought = !reviewed && !look;
|
|
251
251
|
// A game not yet reviewed is marked as brought, for status to ask the AI to go over it; one reviewed is ready, and the chat sleeps on.
|
|
@@ -356,9 +356,12 @@ function analysisShown(served) {
|
|
|
356
356
|
// started again since is gone (Priya). On the table the chat's start is in the chat's own folder.
|
|
357
357
|
const { bell } = served;
|
|
358
358
|
const chat = { ...served.context, folder: served.attached?.chat.folder ?? served.folder };
|
|
359
|
-
const
|
|
359
|
+
const reviewed = isReviewed(record);
|
|
360
|
+
const reviewing = !reviewed && (bell.inTurn || (bell.listening && analystOf(chat, analysis.game) !== undefined));
|
|
361
|
+
// Without a review and with nobody at it, the analysis is incomplete: the page offers to bring it back to the AI (D-109).
|
|
362
|
+
const state = reviewed ? {} : reviewing ? { reviewing: true } : { incomplete: true };
|
|
360
363
|
// How far the book goes, which the page cannot tell without the book's moves: its card names the opening on those moves.
|
|
361
|
-
return { name, pgn: writePgn(record), book: bookPlies(record), ...
|
|
364
|
+
return { name, pgn: writePgn(record), book: bookPlies(record), ...state };
|
|
362
365
|
}
|
|
363
366
|
catch {
|
|
364
367
|
// A game gone from the archive is no longer shown.
|
|
@@ -374,8 +377,8 @@ function lastPlayed(context) {
|
|
|
374
377
|
return { name, record };
|
|
375
378
|
}
|
|
376
379
|
/** A game brought as a PGN, kept to study as analyse_game keeps it: the one already kept when it is the same game, with its review. */
|
|
377
|
-
function kept(served, pgn) {
|
|
378
|
-
analyseGame.run({ pgn }, served.context);
|
|
380
|
+
async function kept(served, pgn) {
|
|
381
|
+
await analyseGame.run({ pgn }, served.context);
|
|
379
382
|
return namedGame(served.context, gameInAnalysis(gameFolder(served.context.home())).game);
|
|
380
383
|
}
|
|
381
384
|
/** The longest PGN the page brings: a long game with its clocks, and the comments a site adds, which go. */
|
package/dist/server/page/page.js
CHANGED
|
@@ -5,11 +5,11 @@ import { createServer } from "node:http";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { positionIn, samePosition } from "../../commands/index.js";
|
|
7
7
|
import { aiNameOf } from "../../core/index.js";
|
|
8
|
-
import { gameStamp, tableTalkStamp } from "../../games/index.js";
|
|
8
|
+
import { forgetAnalyst, gameStamp, tableTalkStamp } from "../../games/index.js";
|
|
9
9
|
import { outdatedHostFiles } from "../../host-files/index.js";
|
|
10
10
|
import { gameFolder } from "../../profiles/index.js";
|
|
11
11
|
import { Bell, bellProbe, pagePorts } from "../bell/index.js";
|
|
12
|
-
import { analysesOf, answerAnalyses } from "./analyses.js";
|
|
12
|
+
import { analysesOf, answerAnalyses, sendSiteGames } from "./analyses.js";
|
|
13
13
|
import { answerChat } from "./attach.js";
|
|
14
14
|
import { attachedSeen, chatSeenOf, pageContext } from "./chat.js";
|
|
15
15
|
import { askNewGame, keepPosted, tellWatchers } from "./events.js";
|
|
@@ -131,6 +131,9 @@ function answer(request, response, served) {
|
|
|
131
131
|
else if (path === "/api/analyses") {
|
|
132
132
|
send(response, 200, json, JSON.stringify(analysesOf(served.context.home())));
|
|
133
133
|
}
|
|
134
|
+
else if (path === "/api/site-games") {
|
|
135
|
+
sendSiteGames(response, served);
|
|
136
|
+
}
|
|
134
137
|
else if (path === "/api/events") {
|
|
135
138
|
keepPosted(request, response, served.watchers);
|
|
136
139
|
// A page that opens reads the player's ratings again after a day (D-106).
|
|
@@ -141,6 +144,9 @@ function answer(request, response, served) {
|
|
|
141
144
|
send(response, 200, json, JSON.stringify(bellProbe(served.folder, served.watchers.size > 0, tableChat(served))));
|
|
142
145
|
}
|
|
143
146
|
else {
|
|
147
|
+
if (url.searchParams.get("analyst") === "gone") {
|
|
148
|
+
forgetGoneAnalyst(served);
|
|
149
|
+
}
|
|
144
150
|
served.bell.listen(request, response);
|
|
145
151
|
}
|
|
146
152
|
}
|
|
@@ -148,6 +154,15 @@ function answer(request, response, served) {
|
|
|
148
154
|
sendFile(response, path);
|
|
149
155
|
}
|
|
150
156
|
}
|
|
157
|
+
/** The chat's hook says no analyst runs any more (D-109): the game it held is free, and its review, unless finished, incomplete. */
|
|
158
|
+
function forgetGoneAnalyst(served) {
|
|
159
|
+
try {
|
|
160
|
+
forgetAnalyst(gameFolder(served.context.home()));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// A data folder that cannot be written keeps the analyst until half an hour has gone by.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
151
166
|
/** The chat at the table, as its probe says it: none on a page a chat serves itself. */
|
|
152
167
|
function tableChat(served) {
|
|
153
168
|
if (served.table === undefined) {
|
package/dist/sites/chesscom.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { SiteReading } from "../profiles/index.ts";
|
|
2
|
-
import { type Fetch } from "./request.ts";
|
|
2
|
+
import { type Fetch } from "./request/index.ts";
|
|
3
3
|
/** What Chess.com says of a name: undefined when it could not be reached. */
|
|
4
4
|
export declare function chesscomReading(account: string, fetch: Fetch, signal: AbortSignal): Promise<SiteReading | undefined>;
|
package/dist/sites/chesscom.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { counted } from "./choice.js";
|
|
2
|
-
import { askSite } from "./request.js";
|
|
2
|
+
import { askSite } from "./request/index.js";
|
|
3
3
|
/** What Chess.com says of a name: undefined when it could not be reached. */
|
|
4
4
|
export async function chesscomReading(account, fetch, signal) {
|
|
5
5
|
const statistics = await askSite(`https://api.chess.com/pub/player/${encodeURIComponent(account.toLowerCase())}/stats`, fetch, signal);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Fetch } from "../request/index.ts";
|
|
2
|
+
import { type SiteGamesAnswer } from "./game.ts";
|
|
3
|
+
/** What Chess.com gives of a name's latest games, the latest first. */
|
|
4
|
+
export declare function chesscomGames(account: string, fetch: Fetch, signal: AbortSignal): Promise<SiteGamesAnswer>;
|