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