chesspal 0.3.5 → 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/README.md +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- 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 +17 -9
- 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 +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- 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/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- 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 +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- 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 +7 -6
- 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/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- 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 +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- 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/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- 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/sounds/sounds.js +1 -1
- package/dist/page/style.css +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 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- 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/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- 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 +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- 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/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- 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 +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +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 +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
|
@@ -1,40 +1,118 @@
|
|
|
1
|
-
// The table's page opened in the player's browser (D-104): in a window of its own, as an application,
|
|
2
|
-
//
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
// The table's page opened in the player's browser (D-104): in a window of its own, as an application, in the default browser when it opens one with --app,
|
|
2
|
+
// else in Chrome or Edge when either is installed; else in a tab of the system's default browser.
|
|
3
|
+
import { execFile, spawn } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { posix, win32 } from "node:path";
|
|
6
6
|
import { openFolder } from "../../home/index.js";
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Opens an address in a window of its own, marked with the table's opening, else in a tab of the default browser, without waiting for it: how it opened.
|
|
9
|
+
* A tab is not marked: a page cannot close a tab it did not open.
|
|
10
|
+
*/
|
|
11
|
+
export async function openInBrowser(url, platform, opening) {
|
|
12
|
+
const browser = await appBrowser(platform, process.env);
|
|
13
|
+
if (browser !== undefined && await started(browser, [`--app=${openedAddress(url, opening)}`])) {
|
|
14
|
+
return "window";
|
|
15
|
+
}
|
|
10
16
|
// Explorer, open and xdg-open take an address as they take a folder, and hand it to the default browser.
|
|
11
|
-
return
|
|
17
|
+
return await openFolder(url, platform) ? "tab" : "none";
|
|
12
18
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The address of the window the table opens, marked so that the page sizes it as it opens, with its opening, so that it closes once a newer window has
|
|
21
|
+
* opened (page/layout/window.ts).
|
|
22
|
+
*/
|
|
23
|
+
export function openedAddress(url, opening) {
|
|
15
24
|
const address = new URL(url);
|
|
16
|
-
address.searchParams.set("opened", "");
|
|
25
|
+
address.searchParams.set("opened", opening === undefined ? "" : String(opening));
|
|
17
26
|
return address.href;
|
|
18
27
|
}
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The browser that opens the table's window: the default one when it is a Chromium that takes --app and is where it installs itself, so that the window
|
|
30
|
+
* opens in the browser the player uses; else Chrome or Edge, the first there is; none when neither is.
|
|
31
|
+
*/
|
|
32
|
+
export async function appBrowser(platform, env, system = realSystem) {
|
|
33
|
+
const chosen = await defaultBrowser(platform, system);
|
|
34
|
+
const tried = platform === "linux" ? [chrome, chromium, edge] : [chrome, edge];
|
|
35
|
+
const browsers = chosen === undefined ? tried : [chosen, ...tried];
|
|
36
|
+
return browsers.flatMap((browser) => browserPaths(browser, platform, env)).find(system.exists);
|
|
37
|
+
}
|
|
38
|
+
const chrome = {
|
|
39
|
+
windows: ["Google", "Chrome", "Application", "chrome.exe"],
|
|
40
|
+
progId: "ChromeHTML",
|
|
41
|
+
mac: "Google Chrome",
|
|
42
|
+
linux: ["google-chrome", "google-chrome-stable"],
|
|
43
|
+
desktop: ["google-chrome"],
|
|
44
|
+
};
|
|
45
|
+
const edge = {
|
|
46
|
+
windows: ["Microsoft", "Edge", "Application", "msedge.exe"],
|
|
47
|
+
progId: "MSEdgeHTM",
|
|
48
|
+
mac: "Microsoft Edge",
|
|
49
|
+
linux: ["microsoft-edge", "microsoft-edge-stable"],
|
|
50
|
+
desktop: ["microsoft-edge"],
|
|
51
|
+
};
|
|
52
|
+
const brave = {
|
|
53
|
+
windows: ["BraveSoftware", "Brave-Browser", "Application", "brave.exe"],
|
|
54
|
+
progId: "BraveHTML",
|
|
55
|
+
mac: "Brave Browser",
|
|
56
|
+
linux: ["brave-browser", "brave-browser-stable", "brave"],
|
|
57
|
+
desktop: ["brave-browser", "brave_brave"],
|
|
58
|
+
};
|
|
59
|
+
const chromium = {
|
|
60
|
+
windows: ["Chromium", "Application", "chrome.exe"],
|
|
61
|
+
progId: "ChromiumHTM",
|
|
62
|
+
mac: "Chromium",
|
|
63
|
+
linux: ["chromium", "chromium-browser"],
|
|
64
|
+
desktop: ["chromium", "chromium-browser", "chromium_chromium"],
|
|
65
|
+
};
|
|
66
|
+
const chromiums = [chrome, edge, brave, chromium];
|
|
67
|
+
// Windows keeps the default browser for http in UserChoiceLatest when it has one, which it then reads in place of UserChoice; macOS is not asked.
|
|
68
|
+
async function defaultBrowser(platform, system) {
|
|
69
|
+
if (platform === "win32") {
|
|
70
|
+
const choices = await system.output("reg", ["query", String.raw `HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http`, "/s"]);
|
|
71
|
+
const progId = progIdIn(choices, String.raw `\UserChoiceLatest\ProgId`) ?? progIdIn(choices, String.raw `\UserChoice`);
|
|
72
|
+
return chromiums.find((browser) => progId === browser.progId || progId?.startsWith(`${browser.progId}.`) === true);
|
|
73
|
+
}
|
|
74
|
+
if (platform === "linux") {
|
|
75
|
+
const desktop = (await system.output("xdg-settings", ["get", "default-web-browser"])).trim().replace(/\.desktop$/, "");
|
|
76
|
+
return chromiums.find((browser) => browser.desktop.includes(desktop));
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// The ProgId under the key of reg's answer that ends so.
|
|
81
|
+
function progIdIn(choices, key) {
|
|
82
|
+
let inKey = false;
|
|
83
|
+
for (const line of choices.split(/\r?\n/)) {
|
|
84
|
+
if (line.startsWith("HKEY_")) {
|
|
85
|
+
inKey = line.trimEnd().endsWith(key);
|
|
86
|
+
}
|
|
87
|
+
else if (inKey) {
|
|
88
|
+
const value = /^\s+ProgId\s+REG_SZ\s+(\S+)/.exec(line);
|
|
89
|
+
if (value !== null) {
|
|
90
|
+
return value[1];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
22
95
|
}
|
|
23
|
-
//
|
|
24
|
-
function browserPaths(platform, env) {
|
|
96
|
+
// Where each system installs a browser: Program Files and the user's AppData, the Applications of macOS, the PATH of Linux.
|
|
97
|
+
function browserPaths(browser, platform, env) {
|
|
25
98
|
if (platform === "win32") {
|
|
26
99
|
const roots = [env.ProgramFiles, env["ProgramFiles(x86)"], env.LOCALAPPDATA].filter((root) => root !== undefined && root !== "");
|
|
27
|
-
|
|
28
|
-
return programs.flatMap((program) => roots.map((root) => win32.join(root, ...program)));
|
|
100
|
+
return roots.map((root) => win32.join(root, ...browser.windows));
|
|
29
101
|
}
|
|
30
102
|
if (platform === "darwin") {
|
|
31
103
|
const roots = ["/Applications", ...(env.HOME === undefined ? [] : [posix.join(env.HOME, "Applications")])];
|
|
32
|
-
return
|
|
104
|
+
return roots.map((root) => posix.join(root, `${browser.mac}.app`, "Contents", "MacOS", browser.mac));
|
|
33
105
|
}
|
|
34
106
|
const folders = (env.PATH ?? "").split(":").filter((folder) => folder !== "");
|
|
35
|
-
|
|
36
|
-
return names.flatMap((name) => folders.map((folder) => posix.join(folder, name)));
|
|
107
|
+
return browser.linux.flatMap((name) => folders.map((folder) => posix.join(folder, name)));
|
|
37
108
|
}
|
|
109
|
+
const realSystem = {
|
|
110
|
+
exists: existsSync,
|
|
111
|
+
output: (command, args) => new Promise((done) => {
|
|
112
|
+
// A second at most: the window would rather open in Chrome or Edge than wait on a registry or a desktop that does not answer.
|
|
113
|
+
execFile(command, args, { windowsHide: true, timeout: 1000 }, (error, output) => done(error === null ? output : ""));
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
38
116
|
// A browser started apart from this process, which it outlives: whether it started.
|
|
39
117
|
function started(browser, args) {
|
|
40
118
|
return new Promise((done) => {
|
|
@@ -5,3 +5,8 @@ import type { TableChats } from "../../page/index.ts";
|
|
|
5
5
|
* terminal opened with `chesspal play`, run by `chesspal`, the same Node and script as the table's.
|
|
6
6
|
*/
|
|
7
7
|
export declare function terminalChats(context: Context, chesspal?: readonly string[], env?: NodeJS.ProcessEnv): TableChats;
|
|
8
|
+
/**
|
|
9
|
+
* What the terminal runs: `chesspal play <host>`, with the table's data folder when it has one of its own, as a playtest's: Terminal.app and Windows
|
|
10
|
+
* Terminal start the chat without the table's environment.
|
|
11
|
+
*/
|
|
12
|
+
export declare function playCommand(chesspal: readonly string[], host: string, env: NodeJS.ProcessEnv): string[];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { tableFolder } from "../../../home/index.js";
|
|
2
|
-
import { samePath } from "../../bell/index.js";
|
|
1
|
+
import { samePath, tableFolder } from "../../../home/index.js";
|
|
3
2
|
import { programPath } from "./find.js";
|
|
4
3
|
import { hostChoices, hostNames, isHostName } from "./hosts.js";
|
|
5
4
|
import { askAgain, seatHeld } from "./seat.js";
|
|
@@ -18,15 +17,23 @@ export function terminalChats(context, chesspal = [process.execPath, ...process.
|
|
|
18
17
|
}
|
|
19
18
|
return found.hosts;
|
|
20
19
|
},
|
|
21
|
-
open: async (host) => isHostName(host) && await openTerminal(tableFolder(context.home()),
|
|
20
|
+
open: async (host) => isHostName(host) && await openTerminal(tableFolder(context.home()), playCommand(chesspal, host, env), context.platform, env),
|
|
22
21
|
// Only a chat in the table's folder can be the one chesspal play holds.
|
|
23
22
|
held: (folder) => {
|
|
24
23
|
const seat = samePath(folder, tableFolder(context.home())) ? seatHeld(folder, context.now()) : undefined;
|
|
25
|
-
return seat === undefined ? undefined : { model: seat.model, effort: seat.effort };
|
|
24
|
+
return seat === undefined ? undefined : { model: seat.model, effort: seat.effort, seat: seat.pid };
|
|
26
25
|
},
|
|
27
26
|
relaunch: (folder, host, updated) => samePath(folder, tableFolder(context.home())) && askAgain(folder, context.now(), { ...(host === undefined ? {} : { host }), ...(updated === true ? { updated } : {}) }),
|
|
28
27
|
};
|
|
29
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* What the terminal runs: `chesspal play <host>`, with the table's data folder when it has one of its own, as a playtest's: Terminal.app and Windows
|
|
31
|
+
* Terminal start the chat without the table's environment.
|
|
32
|
+
*/
|
|
33
|
+
export function playCommand(chesspal, host, env) {
|
|
34
|
+
const home = env.CHESSPAL_HOME?.trim();
|
|
35
|
+
return [...chesspal, "play", host, ...(home ? ["--home", home] : [])];
|
|
36
|
+
}
|
|
30
37
|
// Looking on the PATH takes a few milliseconds, and the page asks at every change of the game.
|
|
31
38
|
const lookAgain = 10_000;
|
|
32
39
|
function chatHost(name, platform, env) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Codex's hooks as Chesspal last wrote them, which Codex asks the player to trust: the first time, or changed over ones trusted before. */
|
|
2
|
+
export type HooksNews = "new" | "changed";
|
|
3
|
+
/** Sets the table's folder up as init does, noting when Chesspal wrote Codex's hooks: Codex then asks the player to trust them. */
|
|
4
|
+
export declare function setUpTable(folder: string, platform: string): void;
|
|
5
|
+
/** What Chesspal wrote of Codex's hooks since the player last heard it; the note is taken. One that cannot be read is no note. */
|
|
6
|
+
export declare function takeHooksNews(folder: string): HooksNews | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The table's folder set up as init does, for the table and for `chesspal play` alike: when Chesspal writes Codex's hooks, the first time or again over
|
|
2
|
+
// older ones, a note says so, which the next `chesspal play codex` takes, for the player to hear it whichever of the two wrote them.
|
|
3
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { writeAtomically } from "../../../games/index.js";
|
|
6
|
+
import { codexHooksPath, setUpHosts } from "../../../host-files/index.js";
|
|
7
|
+
/** Sets the table's folder up as init does, noting when Chesspal wrote Codex's hooks: Codex then asks the player to trust them. */
|
|
8
|
+
export function setUpTable(folder, platform) {
|
|
9
|
+
const hooks = codexHooksPath(folder);
|
|
10
|
+
const hadHooks = existsSync(hooks);
|
|
11
|
+
if (setUpHosts(folder, platform).includes(hooks)) {
|
|
12
|
+
// Hooks never trusted stay new, whatever Chesspal wrote over them before Codex asked.
|
|
13
|
+
const news = hadHooks && readNote(folder) !== "new" ? "changed" : "new";
|
|
14
|
+
writeAtomically(join(folder, hooksNoteFile), `${JSON.stringify({ hooks: news })}\n`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** What Chesspal wrote of Codex's hooks since the player last heard it; the note is taken. One that cannot be read is no note. */
|
|
18
|
+
export function takeHooksNews(folder) {
|
|
19
|
+
const news = readNote(folder);
|
|
20
|
+
try {
|
|
21
|
+
rmSync(join(folder, hooksNoteFile), { force: true });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// A note that cannot go now (the table rewrites it) is said again at the next play, which is harmless.
|
|
25
|
+
}
|
|
26
|
+
return news;
|
|
27
|
+
}
|
|
28
|
+
const hooksNoteFile = "codex-hooks-note.json";
|
|
29
|
+
// The note's news, none when there is no note or it cannot be read.
|
|
30
|
+
function readNote(folder) {
|
|
31
|
+
try {
|
|
32
|
+
const hooks = JSON.parse(readFileSync(join(folder, hooksNoteFile), "utf8"))?.hooks;
|
|
33
|
+
return hooks === "new" || hooks === "changed" ? hooks : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/** The hosts the page offers, by their command's name. */
|
|
2
2
|
export declare const hostNames: readonly ["claude", "codex"];
|
|
3
3
|
export type HostName = typeof hostNames[number];
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The models and efforts the player may choose for a host, as its command line names them: aliases, which follow the host's newest models; and the
|
|
6
|
+
* `model` and `effort` a chat starts with until the player chooses, the middle ones.
|
|
7
|
+
*/
|
|
5
8
|
export interface HostChoices {
|
|
6
9
|
readonly models: readonly string[];
|
|
7
10
|
readonly efforts: readonly string[];
|
|
11
|
+
readonly model: string;
|
|
12
|
+
readonly effort: string;
|
|
8
13
|
}
|
|
9
|
-
/** What the player chose for a host on the page: none of either
|
|
14
|
+
/** What the player chose for a host on the page: none of either, or one the host does not offer, is the host's middle one. */
|
|
10
15
|
export interface HostChoice {
|
|
11
16
|
readonly model?: string | undefined;
|
|
12
17
|
readonly effort?: string | undefined;
|
|
@@ -18,8 +23,11 @@ export interface HostChoice {
|
|
|
18
23
|
export declare const hostChoices: Readonly<Record<HostName, HostChoices>>;
|
|
19
24
|
/** Whether a word names a host the page offers. */
|
|
20
25
|
export declare function isHostName(name: unknown): name is HostName;
|
|
21
|
-
/**
|
|
22
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The message a chat opened by the table starts with, in the player's language: the AI answers it by calling status, as every chat does. A chat opened
|
|
28
|
+
* `toAnalyse` the game the page shows is asked for that: told to play, it opened the New game window over the analysis it had just started.
|
|
29
|
+
*/
|
|
30
|
+
export declare function startMessage(language: string | undefined, toAnalyse?: boolean): string;
|
|
23
31
|
/**
|
|
24
32
|
* The start message and the choices the host knows: first for Claude Code, whose options may take the word after; last for Codex, where "resume" is no
|
|
25
33
|
* subcommand, kept from its update screen, which an Enter would accept.
|
|
@@ -30,5 +38,5 @@ export declare function choiceKeys(host: HostName): {
|
|
|
30
38
|
model: string;
|
|
31
39
|
effort: string;
|
|
32
40
|
};
|
|
33
|
-
/** A host's choice as the profile's settings keep it: the values the host offers alone, the rest is
|
|
41
|
+
/** A host's choice as the profile's settings keep it: the values the host offers alone, the rest is its middle one. */
|
|
34
42
|
export declare function choiceIn(settings: Readonly<Record<string, unknown>> | undefined, host: HostName): HostChoice;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The AI hosts the table opens a chat in (D-104): Claude Code and Codex, each with the start message, and the model and effort the player chose on the page
|
|
2
|
-
// for the profile in use
|
|
2
|
+
// for the profile in use, or each host's middle ones until they choose: the chat never starts with whatever the host was left at.
|
|
3
3
|
import { speaksItalian } from "../../../commands/index.js";
|
|
4
4
|
/** The hosts the page offers, by their command's name. */
|
|
5
5
|
export const hostNames = ["claude", "codex"];
|
|
@@ -8,15 +8,21 @@ export const hostNames = ["claude", "codex"];
|
|
|
8
8
|
* models a ChatGPT account lists, which change with its releases.
|
|
9
9
|
*/
|
|
10
10
|
export const hostChoices = {
|
|
11
|
-
claude: { models: ["fable", "opus", "sonnet", "haiku"], efforts: ["low", "medium", "high", "xhigh", "max"] },
|
|
12
|
-
codex: { models: ["gpt-6.1-sol", "gpt-6-astra", "gpt-6.1-terra", "gpt-6-luna"], efforts: ["low", "medium", "high", "xhigh", "max"] },
|
|
11
|
+
claude: { models: ["fable", "opus", "sonnet", "haiku"], efforts: ["low", "medium", "high", "xhigh", "max"], model: "sonnet", effort: "medium" },
|
|
12
|
+
codex: { models: ["gpt-6.1-sol", "gpt-6-astra", "gpt-6.1-terra", "gpt-6-luna"], efforts: ["low", "medium", "high", "xhigh", "max"], model: "gpt-6.1-terra", effort: "medium" },
|
|
13
13
|
};
|
|
14
14
|
/** Whether a word names a host the page offers. */
|
|
15
15
|
export function isHostName(name) {
|
|
16
16
|
return hostNames.includes(name);
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The message a chat opened by the table starts with, in the player's language: the AI answers it by calling status, as every chat does. A chat opened
|
|
20
|
+
* `toAnalyse` the game the page shows is asked for that: told to play, it opened the New game window over the analysis it had just started.
|
|
21
|
+
*/
|
|
22
|
+
export function startMessage(language, toAnalyse = false) {
|
|
23
|
+
if (toAnalyse) {
|
|
24
|
+
return speaksItalian(language) ? "Analizziamo la partita!" : "Let's analyse the game!";
|
|
25
|
+
}
|
|
20
26
|
return speaksItalian(language) ? "Giochiamo!" : "Let's play!";
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
@@ -34,16 +40,16 @@ export function hostArgs(host, message, choice = {}) {
|
|
|
34
40
|
export function choiceKeys(host) {
|
|
35
41
|
return { model: `${host}Model`, effort: `${host}Effort` };
|
|
36
42
|
}
|
|
37
|
-
/** A host's choice as the profile's settings keep it: the values the host offers alone, the rest is
|
|
43
|
+
/** A host's choice as the profile's settings keep it: the values the host offers alone, the rest is its middle one. */
|
|
38
44
|
export function choiceIn(settings, host) {
|
|
39
45
|
const keys = choiceKeys(host);
|
|
40
46
|
const text = (value) => (typeof value === "string" ? value : undefined);
|
|
41
47
|
return offered(host, { model: text(settings?.[keys.model]), effort: text(settings?.[keys.effort]) });
|
|
42
48
|
}
|
|
43
|
-
// The choice kept to the values the host offers.
|
|
49
|
+
// The choice kept to the values the host offers: its middle model and effort where none is chosen, or one it does not offer.
|
|
44
50
|
function offered(host, choice) {
|
|
45
|
-
const
|
|
46
|
-
const model = choice.model !== undefined && models.includes(choice.model) ? choice.model :
|
|
47
|
-
const effort = choice.effort !== undefined && efforts.includes(choice.effort) ? choice.effort :
|
|
51
|
+
const choices = hostChoices[host];
|
|
52
|
+
const model = choice.model !== undefined && choices.models.includes(choice.model) ? choice.model : choices.model;
|
|
53
|
+
const effort = choice.effort !== undefined && choices.efforts.includes(choice.effort) ? choice.effort : choices.effort;
|
|
48
54
|
return { model, effort };
|
|
49
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// The AI hosts a table opens a chat in (D-104): their choices, the terminal they open in, and `chesspal play`, which starts one there.
|
|
2
2
|
export { terminalChats } from "./chats.js";
|
|
3
|
+
export { setUpTable } from "./hooks.js";
|
|
3
4
|
export { isHostName } from "./hosts.js";
|
|
4
5
|
export { endTree, hostLaunch, playHost } from "./play.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ChildProcess } from "node:child_process";
|
|
2
|
-
import type
|
|
2
|
+
import { type Context } from "../../../commands/index.ts";
|
|
3
|
+
import { type HooksNews } from "./hooks.ts";
|
|
3
4
|
import { type HostChoice, type HostName } from "./hosts.ts";
|
|
4
5
|
/**
|
|
5
6
|
* What `chesspal play` starts: the host's program, its arguments, folder and environment, whether it needs cmd, as a .cmd shim does on Windows, and the
|
|
@@ -12,11 +13,14 @@ export interface HostLaunch {
|
|
|
12
13
|
readonly env: NodeJS.ProcessEnv;
|
|
13
14
|
readonly shell: boolean;
|
|
14
15
|
readonly choice: HostChoice;
|
|
16
|
+
/** Chesspal wrote Codex's hooks, the first time or again: Codex runs them only once the player trusts them. */
|
|
17
|
+
readonly hooks?: HooksNews;
|
|
15
18
|
}
|
|
16
19
|
/**
|
|
17
20
|
* 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),
|
|
18
|
-
* or for another model or host, which then takes its place (D-107); after an update, by the Chesspal installed (D-108).
|
|
19
|
-
*
|
|
21
|
+
* or for another model or host, which then takes its place (D-107); after an update, by the Chesspal installed (D-108). Claude Code closed at its
|
|
22
|
+
* start with an error, as when the player keeps No at its question on the folder, is told how to answer and started once more. Ctrl+C is the host's:
|
|
23
|
+
* this process ignores it, or the terminal would close with the chat.
|
|
20
24
|
*/
|
|
21
25
|
export declare function playHost(context: Context, host: HostName): Promise<number>;
|
|
22
26
|
/**
|
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { win32 } from "node:path";
|
|
6
|
+
import { speaksItalian } from "../../../commands/index.js";
|
|
7
|
+
import { endOf, gameInAnalysis, loadGame } from "../../../games/index.js";
|
|
6
8
|
import { tableFolder } from "../../../home/index.js";
|
|
7
|
-
import { setUpHosts } from "../../../host-files/index.js";
|
|
8
9
|
import { activeProfile, gameFolder, languageIn, profileSettings } from "../../../profiles/index.js";
|
|
9
10
|
import { programPath } from "./find.js";
|
|
11
|
+
import { setUpTable, takeHooksNews } from "./hooks.js";
|
|
10
12
|
import { choiceIn, hostArgs, isHostName, startMessage } from "./hosts.js";
|
|
11
13
|
import { askedAgain, leaveSeat, refreshSeat, seatTimes, takeSeat } from "./seat.js";
|
|
12
14
|
import { cmdQuoted } from "./terminal.js";
|
|
13
15
|
/**
|
|
14
16
|
* 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).
|
|
16
|
-
*
|
|
17
|
+
* or for another model or host, which then takes its place (D-107); after an update, by the Chesspal installed (D-108). Claude Code closed at its
|
|
18
|
+
* start with an error, as when the player keeps No at its question on the folder, is told how to answer and started once more. Ctrl+C is the host's:
|
|
19
|
+
* this process ignores it, or the terminal would close with the chat.
|
|
17
20
|
*/
|
|
18
21
|
export async function playHost(context, host) {
|
|
19
22
|
const folder = tableFolder(context.home());
|
|
@@ -32,6 +35,8 @@ export async function playHost(context, host) {
|
|
|
32
35
|
let refreshed = Date.now();
|
|
33
36
|
// Chesspal was updated under this process (D-108): the new one plays on.
|
|
34
37
|
let updated = false;
|
|
38
|
+
// Claude Code was started once more after it closed at its start.
|
|
39
|
+
let startedOnceMore = false;
|
|
35
40
|
const asked = () => {
|
|
36
41
|
if (Date.now() - refreshed >= seatTimes.refresh) {
|
|
37
42
|
refreshed = Date.now();
|
|
@@ -46,6 +51,7 @@ export async function playHost(context, host) {
|
|
|
46
51
|
for (;;) {
|
|
47
52
|
// A request left from a second click while the host before was ending is not for this one.
|
|
48
53
|
askedAgain(folder, process.pid);
|
|
54
|
+
const startedAt = context.now();
|
|
49
55
|
const ran = await started(context, playing, asked).catch((error) => {
|
|
50
56
|
if (before === undefined) {
|
|
51
57
|
throw error;
|
|
@@ -60,7 +66,16 @@ export async function playHost(context, host) {
|
|
|
60
66
|
}
|
|
61
67
|
before = undefined;
|
|
62
68
|
if (!ran.again) {
|
|
63
|
-
|
|
69
|
+
if (playing !== "claude" || ran.code === 0 || context.now().getTime() - startedAt.getTime() >= closedAtStart) {
|
|
70
|
+
return ran.code;
|
|
71
|
+
}
|
|
72
|
+
const words = speaksItalian(context.language) ? closedAtStartWords.italian : closedAtStartWords.english;
|
|
73
|
+
process.stdout.write(`\n${words.join("\n")}\n`);
|
|
74
|
+
if (startedOnceMore) {
|
|
75
|
+
return ran.code;
|
|
76
|
+
}
|
|
77
|
+
startedOnceMore = true;
|
|
78
|
+
continue;
|
|
64
79
|
}
|
|
65
80
|
if (updated) {
|
|
66
81
|
terminal.restore();
|
|
@@ -120,23 +135,60 @@ export function hostLaunch(context, host, env = process.env, exists) {
|
|
|
120
135
|
if (found === undefined) {
|
|
121
136
|
throw new Error(`${hostTitle[host]} is not installed on this computer, or not on its PATH: install it (${installHint[host]}), then try again.`);
|
|
122
137
|
}
|
|
123
|
-
|
|
138
|
+
setUpTable(folder, context.platform);
|
|
139
|
+
// Codex's hooks written, by this play or by the table before it: the note is for Codex's chat, and waits for it.
|
|
140
|
+
const news = host === "codex" ? takeHooksNews(folder) : undefined;
|
|
141
|
+
const hooks = news === undefined ? {} : { hooks: news };
|
|
124
142
|
const choice = choiceIn(profileSettings(home, activeProfile(home).id), host);
|
|
125
|
-
const
|
|
143
|
+
const games = gameFolder(home);
|
|
144
|
+
const args = hostArgs(host, startMessage(languageIn(games, context.language), analysisAsked(games)), choice);
|
|
126
145
|
// An effort the player set for every chat would win over the one chosen on the page: spawn leaves out a variable that is undefined.
|
|
127
146
|
const effort = host === "claude" && choice.effort !== undefined ? { CLAUDE_CODE_EFFORT_LEVEL: undefined } : {};
|
|
128
|
-
|
|
147
|
+
// The seat this play holds, by which its chat's server tells the table it is the chat held, and not another opened in the same folder.
|
|
148
|
+
const childEnv = { ...env, ...Object.fromEntries(sessionMarks.map((name) => [name, undefined])), ...effort, CHESSPAL_SEAT: String(process.pid) };
|
|
129
149
|
const script = npmScript(host, found, context.platform, exists ?? existsSync);
|
|
130
150
|
if (script !== undefined) {
|
|
131
|
-
return { program: process.execPath, args: [script, ...args], folder, env: childEnv, shell: false, choice };
|
|
151
|
+
return { program: process.execPath, args: [script, ...args], folder, env: childEnv, shell: false, choice, ...hooks };
|
|
152
|
+
}
|
|
153
|
+
return { program: found, args, folder, env: childEnv, shell: context.platform === "win32" && /\.(cmd|bat)$/i.test(found), choice, ...hooks };
|
|
154
|
+
}
|
|
155
|
+
/** Whether the board page shows a game in analysis, none going on: the chat opened there is for that game, brought to review or to talk over, not for a new one. */
|
|
156
|
+
function analysisAsked(folder) {
|
|
157
|
+
try {
|
|
158
|
+
const current = loadGame(folder);
|
|
159
|
+
return (current === undefined || endOf(current.game) !== undefined) && gameInAnalysis(folder) !== undefined;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// A game that cannot be read is status's to report.
|
|
163
|
+
return false;
|
|
132
164
|
}
|
|
133
|
-
return { program: found, args, folder, env: childEnv, shell: context.platform === "win32" && /\.(cmd|bat)$/i.test(found), choice };
|
|
134
165
|
}
|
|
135
166
|
const hostTitle = { claude: "Claude Code", codex: "Codex" };
|
|
167
|
+
// What the player is told before Codex asks about the hooks Chesspal wrote: the first time, and when they changed.
|
|
168
|
+
const hooksWords = {
|
|
169
|
+
new: {
|
|
170
|
+
english: "Codex will ask about Chesspal's two hooks, small helpers that let it see your moves on the board: choose Trust all and continue.",
|
|
171
|
+
italian: "Codex ti chiederà dei due «hook» di Chesspal, piccoli aiuti che gli fanno vedere le tue mosse sulla scacchiera: scegli Trust all and continue.",
|
|
172
|
+
},
|
|
173
|
+
changed: {
|
|
174
|
+
english: "Chesspal's hooks for Codex have changed: when Codex asks about them, choose Trust all again.",
|
|
175
|
+
italian: "Gli «hook» di Chesspal per Codex sono cambiati: quando Codex te lo chiede, scegli di nuovo Trust all.",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
// What the player is told when Claude Code closed at its start: its question on the folder starts on No.
|
|
179
|
+
const closedAtStartWords = {
|
|
180
|
+
english: ["Claude Code closed as soon as it opened.", 'If it asks whether you trust this folder, go down to "Yes, I trust this folder" with the arrow keys, then press Enter.'],
|
|
181
|
+
italian: ["Claude Code si è chiuso appena aperto.", "Se ti chiede se ti fidi della cartella, scendi con le frecce su «Yes, I trust this folder», poi premi Invio."],
|
|
182
|
+
};
|
|
136
183
|
// 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
184
|
async function started(context, host, again) {
|
|
138
185
|
const launch = hostLaunch(context, host);
|
|
139
186
|
takeSeat(launch.folder, { pid: process.pid, host, ...launch.choice });
|
|
187
|
+
// Codex asks the player to trust the hooks Chesspal wrote: said here, above its question, as init says it.
|
|
188
|
+
if (launch.hooks !== undefined) {
|
|
189
|
+
const words = hooksWords[launch.hooks];
|
|
190
|
+
process.stdout.write(`${speaksItalian(context.language) ? words.italian : words.english}\n`);
|
|
191
|
+
}
|
|
140
192
|
return runHost(launch, again, context.platform).catch((error) => {
|
|
141
193
|
throw new Error(`${hostTitle[host]} did not start: ${error.message}`);
|
|
142
194
|
});
|
|
@@ -167,6 +219,8 @@ function playUpdated(host) {
|
|
|
167
219
|
const killAfter = 3_000;
|
|
168
220
|
// A play process that ends sooner never got to its host.
|
|
169
221
|
const quickExit = 10_000;
|
|
222
|
+
// Claude Code ends 1.5 s after No at its question on the folder, a chat the player ends ends with no error (Priya): a minute is the time to read it.
|
|
223
|
+
const closedAtStart = 60_000;
|
|
170
224
|
// A host ended in the middle leaves the modes it set: colours, scroll region, synchronized output, bracketed paste, focus and mouse reports, kitty's
|
|
171
225
|
// keyboard flags, its own screen, a hidden cursor (Priya). All off, and the screen cleared, as a new chat.
|
|
172
226
|
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";
|
|
@@ -25,6 +25,8 @@ export declare function iconAsked(home: string, machine?: string): boolean;
|
|
|
25
25
|
export declare function noteIconAsked(home: string, machine?: string): void;
|
|
26
26
|
/** Notes the icon made on this machine, its command and files, which asks no more there. */
|
|
27
27
|
export declare function noteIconMade(home: string, command: IconCommand, files: readonly string[], machine?: string): void;
|
|
28
|
+
/** Takes Chesspal's icon off the desktop and the menu of this machine, the files noted when it was made: those it removed. */
|
|
29
|
+
export declare function removeIcons(home: string, machine?: string): string[];
|
|
28
30
|
/**
|
|
29
31
|
* Whether the icon made on this machine runs another Node or script than `command`, which a Node manager or an update may have taken away, and is still
|
|
30
32
|
* there: then it is made again. One the player removed stays removed.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Chesspal's icon on the desktop and in the system's menu (D-104): it opens the table as `chesspal` alone does, with no console, on Windows, macOS and Linux.
|
|
2
2
|
// The command line asks once a machine whether to put it there.
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import { hostname } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { writeAtomically } from "../../../games/index.js";
|
|
8
8
|
import { tableFolder } from "../../../home/index.js";
|
|
@@ -46,6 +46,18 @@ export function noteIconMade(home, command, files, machine = hostname()) {
|
|
|
46
46
|
const asked = notes.asked.includes(machine) ? notes.asked : [...notes.asked, machine];
|
|
47
47
|
writeIconNotes(home, { asked, made: { ...notes.made, [machine]: { command: commandLine(command), files: [...files] } } });
|
|
48
48
|
}
|
|
49
|
+
/** Takes Chesspal's icon off the desktop and the menu of this machine, the files noted when it was made: those it removed. */
|
|
50
|
+
export function removeIcons(home, machine = hostname()) {
|
|
51
|
+
const notes = iconNotes(home);
|
|
52
|
+
// Only Chesspal's own files, by their names, and links too, whose target the player may have thrown away already.
|
|
53
|
+
const removed = (notes.made[machine]?.files ?? []).filter((file) => iconName.test(basename(file)) && lstatSync(file, { throwIfNoEntry: false }) !== undefined);
|
|
54
|
+
// On macOS the icon is an application, a folder.
|
|
55
|
+
for (const file of removed) {
|
|
56
|
+
rmSync(file, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
writeIconNotes(home, { ...notes, made: Object.fromEntries(Object.entries(notes.made).filter(([name]) => name !== machine)) });
|
|
59
|
+
return removed;
|
|
60
|
+
}
|
|
49
61
|
/**
|
|
50
62
|
* Whether the icon made on this machine runs another Node or script than `command`, which a Node manager or an update may have taken away, and is still
|
|
51
63
|
* there: then it is made again. One the player removed stays removed.
|
|
@@ -68,6 +80,8 @@ function writeIconNotes(home, notes) {
|
|
|
68
80
|
mkdirSync(tableFolder(home), { recursive: true });
|
|
69
81
|
writeAtomically(notesFile(home), `${JSON.stringify(notes, null, 2)}\n`);
|
|
70
82
|
}
|
|
83
|
+
// The names of the files an icon is made of, on Windows, Linux and macOS: removing touches no other, whatever the notes say.
|
|
84
|
+
const iconName = /^chesspal\.(lnk|desktop|app)$/i;
|
|
71
85
|
// The notes kept; none when the file is missing or cannot be read, which only asks again.
|
|
72
86
|
function iconNotes(home) {
|
|
73
87
|
const file = notesFile(home);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { appBrowser, openedAddress } from "./browser.ts";
|
|
1
|
+
export { appBrowser, type BrowserSystem, type Opened, openedAddress } from "./browser.ts";
|
|
2
2
|
export { hostLaunch, isHostName, playHost, terminalChats } from "./hosts/index.ts";
|
|
3
|
-
export { iconAsked, type IconCommand, iconCommand, type IconPlaces, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "./icon/index.ts";
|
|
3
|
+
export { iconAsked, type IconCommand, iconCommand, type IconPlaces, iconToRemake, makeIcons, noteIconAsked, noteIconMade, removeIcons } from "./icon/index.ts";
|
|
4
4
|
export { noteFailure, openTable, processLauncher, type TableLauncher } from "./launch.ts";
|
|
5
5
|
export { attachTable, tableToJoin } from "./link.ts";
|
|
6
6
|
export { startTable, type Table, type TableTimes, tableTimes } from "./table.ts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The table (D-104): the board page in a process of its own, started by `chesspal` alone, with no chat. Above server/page, which serves the page in either process.
|
|
2
2
|
export { appBrowser, openedAddress } from "./browser.js";
|
|
3
3
|
export { hostLaunch, isHostName, playHost, terminalChats } from "./hosts/index.js";
|
|
4
|
-
export { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "./icon/index.js";
|
|
4
|
+
export { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade, removeIcons } from "./icon/index.js";
|
|
5
5
|
export { noteFailure, openTable, processLauncher } from "./launch.js";
|
|
6
6
|
export { attachTable, tableToJoin } from "./link.js";
|
|
7
7
|
export { startTable, tableTimes } from "./table.js";
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { type Context } from "../../commands/index.ts";
|
|
2
|
+
import { type Opened } from "./browser.ts";
|
|
2
3
|
/** What opening the table does outside this process: start the table apart, open the browser, wait. The tests give their own. */
|
|
3
4
|
export interface TableLauncher {
|
|
4
5
|
/** Starts `chesspal table` apart from this process, which it outlives, in the data folder. */
|
|
5
6
|
start(home: string): void;
|
|
6
|
-
/** Opens the page in the browser:
|
|
7
|
-
open(url: string): Promise<
|
|
7
|
+
/** Opens the page in the browser, its window marked with its opening when the table gave one: how it opened. */
|
|
8
|
+
open(url: string, opening?: number): Promise<Opened>;
|
|
8
9
|
wait(milliseconds: number): Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Opens the table's page in the browser, the table started first when none answers, or when the one that does is of another version, which is asked to
|
|
12
|
-
* quit; a table of this version another `chesspal` started meanwhile is as good.
|
|
13
|
-
* within ten seconds throws.
|
|
13
|
+
* quit; a table of this version another `chesspal` started meanwhile is as good. The window opened last stays, the table's older ones close, and none opens
|
|
14
|
+
* while another is opening (D-104). What to tell the player, in their language; a table that does not start within ten seconds throws.
|
|
14
15
|
*/
|
|
15
16
|
export declare function openTable(context: Context, launcher?: TableLauncher): Promise<string>;
|
|
16
17
|
/**
|