chesspal 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +39 -13
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +46 -13
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +40 -18
- package/dist/commands/answer-lines.d.ts +2 -0
- package/dist/commands/answer-lines.js +13 -3
- package/dist/commands/coach.js +4 -2
- package/dist/commands/context.d.ts +9 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -5
- package/dist/commands/game-summary.d.ts +2 -0
- package/dist/commands/game-summary.js +4 -0
- package/dist/commands/index.d.ts +3 -2
- package/dist/commands/index.js +7 -4
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +46 -27
- package/dist/commands/study.js +8 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/review.d.ts +2 -0
- package/dist/core/review.js +5 -1
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +26 -3
- package/dist/games/analysis.js +41 -6
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +10 -1
- package/dist/games/current-game.js +49 -15
- package/dist/games/files.d.ts +5 -0
- package/dist/games/files.js +42 -1
- package/dist/games/index.d.ts +3 -3
- package/dist/games/index.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +29 -16
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +95 -42
- package/dist/page/game/column.js +5 -1
- package/dist/page/game/plies.js +7 -4
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +7 -10
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +111 -4
- package/dist/page/layout/window.js +45 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +2 -0
- package/dist/page/table/opener.css +65 -0
- package/dist/page/table/opener.js +59 -0
- package/dist/page/talk/chat.css +10 -3
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +4 -2
- package/dist/page/text.js +83 -17
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +100 -52
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +60 -0
- package/dist/server/page/chat.js +79 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +43 -23
- package/dist/server/page/page.d.ts +47 -7
- package/dist/server/page/page.js +69 -48
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +39 -0
- package/dist/server/page/table.js +102 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +31 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +25 -0
- package/dist/server/table/hosts/play.js +152 -0
- package/dist/server/table/hosts/seat.d.ts +25 -0
- package/dist/server/table/hosts/seat.js +74 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +230 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,23 +6,21 @@ Play chess against an AI on a real board, or learn with one.
|
|
|
6
6
|
|
|
7
7
|
## Get started
|
|
8
8
|
|
|
9
|
-
You need [Node.js](https://nodejs.org) 22.13 or newer and an AI agent on your computer, [Claude Code](https://code.claude.com/docs/en/setup) or [Codex](https://developers.openai.com/codex): see [what you need](#what-you-need).
|
|
9
|
+
You need [Node.js](https://nodejs.org) 22.13 or newer and an AI agent on your computer, [Claude Code](https://code.claude.com/docs/en/setup) or [Codex](https://developers.openai.com/codex), signed in with your own subscription: see [what you need](#what-you-need).
|
|
10
|
+
|
|
11
|
+
Open a terminal, the window where you type commands, after installing Node.js: one opened before does not find `npm`. On Windows, open Command Prompt (press Start, type `cmd`, press Enter): PowerShell, on a new computer, refuses npm's commands with "running scripts is disabled on this system", until you type `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` there once and answer yes. On macOS, open Terminal. Then type:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
14
|
npm install -g chesspal
|
|
13
|
-
chesspal
|
|
14
|
-
cd chess
|
|
15
|
-
claude
|
|
15
|
+
chesspal
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
- `npm install -g` installs the `chesspal` command for the whole computer, where your agent finds it.
|
|
19
|
-
- `chesspal
|
|
20
|
-
-
|
|
21
|
-
- The first time,
|
|
22
|
-
|
|
23
|
-
Then **say hello**. The AI answers with the link to the board page: open it in your browser, tell it who you are, and ask for a game. You choose your colour, the clock and the AI's level, and play on the board.
|
|
19
|
+
- `chesspal` opens the board page in your browser. The first time, it offers an icon on your desktop and in your menu of programs: with it, you open the board with a double click, with no terminal.
|
|
20
|
+
- The first time, the board page asks who you are: fill it in and press **Start playing**.
|
|
21
|
+
- Press **Claude Code** or **Codex**: the AI opens in a terminal of its own, at the table; keep it open while you play. The first time, it asks whether you trust the folder: choose **Yes** with the arrow keys and press Enter. With Codex, when it asks about Chesspal's two hooks, choose **Trust all and continue**; on Windows, it also offers to set up its sandbox: choose the first option, and answer yes when Windows asks for permission.
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
Then press **New game**: you choose your colour, the clock and the AI's level, and play on the board. Talk with the AI on the board page or in its terminal.
|
|
26
24
|
|
|
27
25
|
## What it does
|
|
28
26
|
|
|
@@ -46,9 +44,10 @@ Chesspal works with any agent that supports MCP tools and can work with the file
|
|
|
46
44
|
|
|
47
45
|
## Good to know
|
|
48
46
|
|
|
49
|
-
- **For the next game**,
|
|
47
|
+
- **For the next game**, press New game: the AI the board page opened starts again with a clean chat by itself, once it has stopped working. In a chat you opened yourself, type `/clear` first: a long chat makes the AI slower and costs you more.
|
|
48
|
+
- **To play in a folder of your own**, as with the Claude desktop app, type `chesspal init chess`, then open your agent in the folder `chess` and say hello: the AI answers with the link to the board page. Any name will do; your games are kept [elsewhere](#where-your-games-are).
|
|
50
49
|
- **A move written in the chat** counts too: `e4`, `Nf3`, in the international notation (K Q R B N in every language), or in words.
|
|
51
|
-
- **To update**, close the
|
|
50
|
+
- **To update**, close the AI's terminal, run `npm install -g chesspal` again, then `chesspal`. In a folder of your own, run `chesspal init` there too: it updates its own lines and keeps what you wrote there. In Codex, allow the hooks again when it says so.
|
|
52
51
|
|
|
53
52
|
## Where your games are
|
|
54
53
|
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runCommandLine } from "./command-line.js";
|
|
3
3
|
const args = process.argv.slice(2);
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
if (args.length ===
|
|
4
|
+
// The table's own words (D-104): alone or open, its page; table, icon, play <host>. The host's: mcp, its server; listen, its doorbell; chat, how the chat
|
|
5
|
+
// started. Everything else is a command.
|
|
6
|
+
if (args.length === 0) {
|
|
7
|
+
await openTableHere(true);
|
|
8
|
+
}
|
|
9
|
+
else if (args.length === 1 && args[0] === "open") {
|
|
10
|
+
// The icon's: its console has no window, where a question would hang unseen.
|
|
11
|
+
await openTableHere(false);
|
|
12
|
+
}
|
|
13
|
+
else if (args.length === 1 && args[0] === "table") {
|
|
14
|
+
await serveTable();
|
|
15
|
+
}
|
|
16
|
+
else if (args.length === 1 && args[0] === "icon") {
|
|
17
|
+
await putIconHere();
|
|
18
|
+
}
|
|
19
|
+
else if (args.length === 2 && args[0] === "play" && !args[1].startsWith("-")) {
|
|
20
|
+
await playHere(args[1]);
|
|
21
|
+
}
|
|
22
|
+
else if (args.length === 1 && args[0] === "mcp") {
|
|
7
23
|
// The server and its SDK load only here: every other command starts faster without them.
|
|
8
24
|
const { startMcpServer } = await import("../server/mcp.js");
|
|
9
25
|
await startMcpServer();
|
|
@@ -26,3 +42,67 @@ else {
|
|
|
26
42
|
}
|
|
27
43
|
process.exitCode = exitCode;
|
|
28
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Opens the table's page, the table started apart when none answers, and returns: the terminal stays free. From a terminal, the first time on a machine
|
|
47
|
+
* it asks first whether to put the icon on the desktop, before the page takes the screen; from the icon, which nobody reads, what went wrong is noted.
|
|
48
|
+
*/
|
|
49
|
+
async function openTableHere(fromTerminal) {
|
|
50
|
+
const [{ noteOpenFailed, openTable }, { defaultContext }, { offerIcon }] = await Promise.all([import("../server/table/index.js"), import("../commands/index.js"), import("./icon.js")]);
|
|
51
|
+
const context = defaultContext();
|
|
52
|
+
try {
|
|
53
|
+
const icon = fromTerminal ? await offerIcon(context) : undefined;
|
|
54
|
+
if (icon !== undefined) {
|
|
55
|
+
console.log(icon);
|
|
56
|
+
}
|
|
57
|
+
console.log(await openTable(context));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(error.message);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
if (!fromTerminal) {
|
|
63
|
+
noteOpenFailed(context, error.message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Puts the icon on the desktop and in the menu. */
|
|
68
|
+
async function putIconHere() {
|
|
69
|
+
const [{ defaultContext }, { iconsMade }] = await Promise.all([import("../commands/index.js"), import("./icon.js")]);
|
|
70
|
+
const { said, failed } = await iconsMade(defaultContext());
|
|
71
|
+
if (failed) {
|
|
72
|
+
console.error(said);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log(said);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** A chat at the table in this terminal, in the host named, until it ends: what the table's buttons run. */
|
|
80
|
+
async function playHere(host) {
|
|
81
|
+
const [{ isHostName, playHost }, { defaultContext }] = await Promise.all([import("../server/table/index.js"), import("../commands/index.js")]);
|
|
82
|
+
if (!isHostName(host)) {
|
|
83
|
+
console.error(`chesspal play takes claude or codex, not ${host}.`);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
process.exitCode = await playHost(defaultContext(), host);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error(error.message);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** The table, until it closes: `chesspal` alone starts it apart from the terminal. */
|
|
96
|
+
async function serveTable() {
|
|
97
|
+
const [{ startTable, tableTimes, terminalChats }, { defaultContext }] = await Promise.all([import("../server/table/index.js"), import("../commands/index.js")]);
|
|
98
|
+
try {
|
|
99
|
+
const context = defaultContext();
|
|
100
|
+
const table = await startTable(context, tableTimes, terminalChats(context));
|
|
101
|
+
console.log(table === undefined ? "Another Chesspal table, started at the same time, is open: this one closes." : `Chesspal's table: ${table.url}`);
|
|
102
|
+
await table?.closed;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(error.message);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/cli/command-line.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
// The `chesspal` command line: a command's input read from the arguments, its result written for a person.
|
|
2
2
|
import { parseArgs } from "node:util";
|
|
3
3
|
import { baseType, commandLineFieldProblem, commands, defaultContext, inputProblems, isBoolean, isList, takesNumbers } from "../commands/index.js";
|
|
4
|
+
import { rerunIfChanged } from "../games/index.js";
|
|
4
5
|
import { version } from "../version.js";
|
|
5
|
-
// The commands that exist only on the command line: cli.ts starts the server, the doorbell or the chat's
|
|
6
|
+
// The commands that exist only on the command line: cli.ts starts the table, opens its page, puts its icon, starts the server, the doorbell or the chat's
|
|
7
|
+
// note, when one comes alone.
|
|
6
8
|
const mcpDescription = "Start the MCP server for an AI host. Through it the host uses these commands as tools; the host starts it, with no options.";
|
|
7
9
|
const listenDescription = "Wait for the player's move on the board page and wake the chat. Claude Code starts it at the end of each turn, and Codex with --codex, as init sets them up.";
|
|
8
10
|
const chatDescription = "Note how the chat started: new, cleared, resumed or compacted. Claude Code and Codex run it when a chat starts, as init sets them up.";
|
|
11
|
+
const tableDescription = "Serve the board page with no chat, until no browser has had it open for five minutes. `chesspal` alone starts it apart and opens the page.";
|
|
12
|
+
const openDescription = "Open the board page as `chesspal` alone does, without asking about the icon: what the icon runs.";
|
|
13
|
+
const iconDescription = "Put Chesspal's icon on the desktop and in the menu: it opens the board page as `chesspal` alone does, with no terminal.";
|
|
14
|
+
const playDescription = "Play at the table in Claude Code or Codex, in this terminal: what the page's buttons open, with the model and effort chosen there; New game on the page starts the chat again, clean.";
|
|
15
|
+
// Those of them that take no options, by name.
|
|
16
|
+
const plainCommands = new Map([["mcp", mcpDescription], ["chat", chatDescription], ["table", tableDescription], ["open", openDescription], ["icon", iconDescription]]);
|
|
9
17
|
/** The `chesspal` command line: finds the command, validates its input, runs it and renders the result. */
|
|
10
18
|
export async function runCommandLine(argv, context = defaultContext(), list = commands) {
|
|
11
19
|
const [commandName, ...args] = argv;
|
|
@@ -15,19 +23,21 @@ export async function runCommandLine(argv, context = defaultContext(), list = co
|
|
|
15
23
|
if (commandName === "--version" || commandName === "-v") {
|
|
16
24
|
return success(version);
|
|
17
25
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const plain = plainCommands.get(commandName);
|
|
27
|
+
if (plain !== undefined) {
|
|
28
|
+
// A host that adds options to mcp would get this text instead of the protocol: it must see the failure.
|
|
29
|
+
const help = `chesspal ${commandName}\n\n${plain}`;
|
|
30
|
+
return args.length === 0 || args.includes("--help") || args.includes("-h") ? success(help) : failure(`chesspal ${commandName} takes no options: ${args.join(" ")}.\n\n${help}`);
|
|
22
31
|
}
|
|
23
32
|
if (commandName === "listen") {
|
|
24
33
|
const help = `chesspal listen [--codex]\n\n${listenDescription}`;
|
|
25
34
|
const known = args.every((arg) => ["--codex", "--help", "-h"].includes(arg));
|
|
26
35
|
return known ? success(help) : failure(`chesspal listen takes only --codex: ${args.join(" ")}.\n\n${help}`);
|
|
27
36
|
}
|
|
28
|
-
if (commandName === "
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
if (commandName === "play") {
|
|
38
|
+
// cli.ts runs `chesspal play <host>`; here come the help and what it refuses.
|
|
39
|
+
const help = `chesspal play <claude|codex>\n\n${playDescription}`;
|
|
40
|
+
return args.includes("--help") || args.includes("-h") ? success(help) : failure(`chesspal play takes one host, claude or codex: ${args.join(" ") || "none given"}.\n\n${help}`);
|
|
31
41
|
}
|
|
32
42
|
const command = list.find((candidate) => candidate.name === commandName);
|
|
33
43
|
if (!command) {
|
|
@@ -51,7 +61,8 @@ async function runCommand(command, args, context) {
|
|
|
51
61
|
const written = (name) => (positionals.includes(name) ? `<${name}>` : `--${name}`);
|
|
52
62
|
throw refused(command, inputProblems(command.name, parsed.error.issues, input, written));
|
|
53
63
|
}
|
|
54
|
-
|
|
64
|
+
// A game another process changed under the command is read again, and the command run once more.
|
|
65
|
+
const result = await rerunIfChanged(() => command.run(parsed.data, context));
|
|
55
66
|
return asJson ? JSON.stringify(result, null, 2) : command.format(result, context);
|
|
56
67
|
}
|
|
57
68
|
/** An input refused, in the words the MCP server uses (inputProblems), with the command's usage. */
|
|
@@ -145,8 +156,14 @@ function generalHelp(list) {
|
|
|
145
156
|
return [
|
|
146
157
|
`chesspal ${version}`,
|
|
147
158
|
"",
|
|
159
|
+
"Run `chesspal` alone to open the board page in your browser.",
|
|
160
|
+
"",
|
|
148
161
|
"Commands:",
|
|
149
162
|
...list.map((command) => line(command.name, command.description)),
|
|
163
|
+
line("table", tableDescription),
|
|
164
|
+
line("open", openDescription),
|
|
165
|
+
line("icon", iconDescription),
|
|
166
|
+
line("play", playDescription),
|
|
150
167
|
line("mcp", mcpDescription),
|
|
151
168
|
line("listen", listenDescription),
|
|
152
169
|
line("chat", chatDescription),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Context } from "../commands/index.ts";
|
|
2
|
+
import { type IconPlaces } from "../server/table/index.ts";
|
|
3
|
+
/** Where `chesspal` alone runs: its input and output, which ask only when both are a terminal, and its environment. The tests give their own. */
|
|
4
|
+
export interface Terminal {
|
|
5
|
+
readonly input: NodeJS.ReadableStream & {
|
|
6
|
+
readonly isTTY?: boolean;
|
|
7
|
+
};
|
|
8
|
+
readonly output: NodeJS.WritableStream & {
|
|
9
|
+
readonly isTTY?: boolean;
|
|
10
|
+
};
|
|
11
|
+
readonly env: NodeJS.ProcessEnv;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Asks once a machine, in a terminal on a screen, whether to put the icon on the desktop and in the menu, and puts it there on a yes: what to tell the
|
|
15
|
+
* player, if anything. Once asked, an icon made by another Node or script is made again, without a word; a playtest's data folder is never asked about.
|
|
16
|
+
*/
|
|
17
|
+
export declare function offerIcon(context: Context, terminal?: Terminal, places?: IconPlaces): Promise<string | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Puts the icon on the desktop and in the menu, and asks no more on this machine: what to tell the player, the files made or why there are none, and
|
|
20
|
+
* whether it failed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function iconsMade(context: Context, places?: IconPlaces): Promise<{
|
|
23
|
+
said: string;
|
|
24
|
+
failed: boolean;
|
|
25
|
+
}>;
|
package/dist/cli/icon.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// The icon from the command line (D-104): `chesspal` alone asks, in a terminal and once a machine, whether to put it on the desktop and in the menu;
|
|
2
|
+
// `chesspal icon` puts it there whenever the player wants it.
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { styleText } from "node:util";
|
|
5
|
+
import { speaksItalian } from "../commands/index.js";
|
|
6
|
+
import { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "../server/table/index.js";
|
|
7
|
+
/**
|
|
8
|
+
* Asks once a machine, in a terminal on a screen, whether to put the icon on the desktop and in the menu, and puts it there on a yes: what to tell the
|
|
9
|
+
* player, if anything. Once asked, an icon made by another Node or script is made again, without a word; a playtest's data folder is never asked about.
|
|
10
|
+
*/
|
|
11
|
+
export async function offerIcon(context, terminal = processTerminal(), places) {
|
|
12
|
+
const home = context.home();
|
|
13
|
+
// The icon would open the table of the player's Documents, not the playtest's.
|
|
14
|
+
if (terminal.env.CHESSPAL_HOME?.trim()) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (iconAsked(home)) {
|
|
18
|
+
if (iconToRemake(home, iconCommand(home))) {
|
|
19
|
+
await iconsMade(context, places);
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (!onScreen(context.platform, terminal)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const words = wordsFor(context);
|
|
27
|
+
const reader = createInterface({ input: terminal.input, output: terminal.output });
|
|
28
|
+
const answer = await reader.question(words.question).finally(() => reader.close());
|
|
29
|
+
noteIconAsked(home);
|
|
30
|
+
return yes(answer) ? (await iconsMade(context, places)).said : words.later;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Puts the icon on the desktop and in the menu, and asks no more on this machine: what to tell the player, the files made or why there are none, and
|
|
34
|
+
* whether it failed.
|
|
35
|
+
*/
|
|
36
|
+
export async function iconsMade(context, places) {
|
|
37
|
+
const words = wordsFor(context);
|
|
38
|
+
try {
|
|
39
|
+
const home = context.home();
|
|
40
|
+
const command = iconCommand(home);
|
|
41
|
+
const made = await makeIcons(context.platform, command, places);
|
|
42
|
+
noteIconMade(home, command, made);
|
|
43
|
+
return { said: `${styleText(["bold", "green"], `♜ ${words.made}`)}\n${styleText("dim", made.map((file) => ` ${file}`).join("\n"))}`, failed: false };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { said: words.failed(error.message), failed: true };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function processTerminal() {
|
|
50
|
+
return { input: process.stdin, output: process.stdout, env: process.env };
|
|
51
|
+
}
|
|
52
|
+
// A terminal the player sees: on Linux, one of a desktop, not one reached through SSH or WSL, where there is no desktop to put the icon on.
|
|
53
|
+
function onScreen(platform, terminal) {
|
|
54
|
+
const desktop = platform !== "linux" || Boolean(terminal.env.DISPLAY || terminal.env.WAYLAND_DISPLAY);
|
|
55
|
+
return terminal.input.isTTY === true && terminal.output.isTTY === true && desktop;
|
|
56
|
+
}
|
|
57
|
+
// Yes unless it says no, in either language: Enter, "ok" and "sure" are yes, since the question is not asked again (Zoe).
|
|
58
|
+
function yes(answer) {
|
|
59
|
+
return !answer.trim().toLowerCase().startsWith("n");
|
|
60
|
+
}
|
|
61
|
+
// The words for the player (Zoe), with the menu as their system names it.
|
|
62
|
+
function wordsFor(context) {
|
|
63
|
+
const system = context.platform === "win32" ? "windows" : context.platform === "darwin" ? "mac" : "linux";
|
|
64
|
+
if (speaksItalian(context.language)) {
|
|
65
|
+
const where = { windows: "nel menu Start", mac: "in Applicazioni", linux: "nel menu delle applicazioni" }[system];
|
|
66
|
+
return {
|
|
67
|
+
question: `Vuoi l'icona di Chesspal sul desktop e ${where}? Con un doppio clic apre la scacchiera. (Invio = sì, n = no) `,
|
|
68
|
+
made: `Fatto: Chesspal è sul desktop e ${where}.`,
|
|
69
|
+
later: "Va bene, niente icona. Se la vuoi più avanti, scrivi chesspal icon.",
|
|
70
|
+
failed: (reason) => `Chesspal non è riuscito a mettere l'icona (${reason}). Riprova con chesspal icon.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const where = { windows: "your Start menu", mac: "Applications", linux: "your applications menu" }[system];
|
|
74
|
+
return {
|
|
75
|
+
question: `Add a Chesspal icon to your desktop and ${where}? Double-click it to open the board. [Y/n] `,
|
|
76
|
+
made: `Done: Chesspal is on your desktop and in ${where}.`,
|
|
77
|
+
later: "All right, no icon. To add it later, type chesspal icon.",
|
|
78
|
+
failed: (reason) => `Chesspal could not add its icon (${reason}). Try again with chesspal icon.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type CurrentGame } from "../games/index.ts";
|
|
2
2
|
import type { Context } from "./context.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { type GameSummary } from "./game-summary.ts";
|
|
4
4
|
/** Whether an analyst agent goes over a game over when the player wants: in Claude Code or Codex, with its agent in the player's folder (D-89, D-98). */
|
|
5
5
|
export declare function analystRuns(context: Context): boolean;
|
|
6
6
|
/**
|
|
7
7
|
* The game over, with the analyst agent that goes over it when the player wants, where it runs (D-89); over, the game from status and ai_say, which come after
|
|
8
|
-
* its end: once
|
|
8
|
+
* its end: once reviewed, or taken by the agent, the chat goes over that review and doesn't start the agent for it (Stefano).
|
|
9
9
|
*/
|
|
10
10
|
export declare function withAnalyst(summary: GameSummary, context: Context, over?: CurrentGame): GameSummary;
|
|
11
11
|
/** How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89). */
|
|
@@ -21,8 +21,25 @@ export declare function agentRuns(context: Context, agent: string): boolean;
|
|
|
21
21
|
* until it starts, since a chat woken by the page may lose the line on its way to its words (Priya).
|
|
22
22
|
*/
|
|
23
23
|
export declare function coachOnEnd(context: Context, folder: string, current: CurrentGame): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* How to start the coach on the player's move that ended the game: whatever the player asks, beside the analyst when the chat starts it too, since a chat on
|
|
26
|
+
* a quick model started only the agent that answered the player (Priya).
|
|
27
|
+
*/
|
|
28
|
+
export declare const coachOnEndLine: string;
|
|
24
29
|
/**
|
|
25
30
|
* How to start the agents, in one message, the one that thinks first, since it needs the time (Priya), and that the player sees nothing of
|
|
26
31
|
* them: their end needs no answer either. The end of the last move's agents, told just after, must not take their place (Priya).
|
|
27
32
|
*/
|
|
28
33
|
export declare function agentsLine(agents: readonly string[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a game is gone over already: reviewed, or taken by an analyst at work on it, whatever the page opened since (Federico); not once its review is
|
|
36
|
+
* deleted, for the analyst to write it again (Stefano).
|
|
37
|
+
*/
|
|
38
|
+
export declare function goneOver(context: Context, game: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The run of the analyst agent at work on a game: the one that took it last, unless its chat has started again since, not only summarized, and took the
|
|
41
|
+
* agent with it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function analystOf(context: Context, game: string): string | undefined;
|
|
44
|
+
/** What stops an analyst agent on a game another analyst has taken: the chat hears of the review from that one (Priya). */
|
|
45
|
+
export declare const anotherAnalyst: string;
|
package/dist/commands/agents.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
// The agents that work beside the chat in Claude Code and Codex (D-98): the analyst that goes over a game over (D-89) and the coach that judges the player's moves (D-88).
|
|
2
2
|
import { isReviewed } from "../core/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { archivedName, coachStarted, gameInAnalysis, lastChatStart } from "../games/index.js";
|
|
4
4
|
import { agentHosts, analystAgent, coachAgent, hasAgent } from "../host-files/index.js";
|
|
5
5
|
import { gameFolder, helpsIn } from "../profiles/index.js";
|
|
6
|
+
import { unplayed } from "./game-summary.js";
|
|
7
|
+
import { namedGame } from "./study.js";
|
|
6
8
|
/** Whether an analyst agent goes over a game over when the player wants: in Claude Code or Codex, with its agent in the player's folder (D-89, D-98). */
|
|
7
9
|
export function analystRuns(context) {
|
|
8
10
|
return agentRuns(context, analystAgent);
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* The game over, with the analyst agent that goes over it when the player wants, where it runs (D-89); over, the game from status and ai_say, which come after
|
|
12
|
-
* its end: once
|
|
14
|
+
* its end: once reviewed, or taken by the agent, the chat goes over that review and doesn't start the agent for it (Stefano).
|
|
13
15
|
*/
|
|
14
16
|
export function withAnalyst(summary, context, over) {
|
|
15
17
|
const { end } = summary;
|
|
16
|
-
|
|
18
|
+
// A game closed before any move has nothing to go over.
|
|
19
|
+
if (end === undefined || unplayed(summary) || !analystRuns(context)) {
|
|
17
20
|
return summary;
|
|
18
21
|
}
|
|
19
|
-
|
|
22
|
+
const archived = over === undefined ? undefined : archivedName(gameFolder(context.home()), over);
|
|
23
|
+
return { ...summary, end: { ...end, analyst: archived !== undefined && goneOver(context, archived) ? "done" : "offer" } };
|
|
20
24
|
}
|
|
21
25
|
/** How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89). */
|
|
22
26
|
export function analystLine(game) {
|
|
@@ -26,7 +30,7 @@ export function analystLine(game) {
|
|
|
26
30
|
}
|
|
27
31
|
/** How the chat goes over a game the analyst agent has reviewed, or is reviewing: from that review, never with another agent (Stefano). */
|
|
28
32
|
export function reviewedLine(game) {
|
|
29
|
-
return "The analyst agent has gone over this game, or is at it:
|
|
33
|
+
return "The analyst agent has gone over this game, or is at it: don't start it for this review. When the player wants it gone over, go over it with them from what "
|
|
30
34
|
+ `it told you, or, in a chat without it, from its review on the board page, which analyse_game ${game === undefined ? "" : `{"game": "${game}"} `}gives.`;
|
|
31
35
|
}
|
|
32
36
|
/** Whether a coach judges the player's moves when the helps are on: in Claude Code or Codex, with its agent in the player's folder (D-80, D-88, D-98). */
|
|
@@ -46,6 +50,13 @@ export function coachOnEnd(context, folder, current) {
|
|
|
46
50
|
const { game, ai } = current;
|
|
47
51
|
return game.outcome.over && game.position.turn === ai && helpsIn(folder) && coachRuns(context) && !coachStarted(folder, current);
|
|
48
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* How to start the coach on the player's move that ended the game: whatever the player asks, beside the analyst when the chat starts it too, since a chat on
|
|
55
|
+
* a quick model started only the agent that answered the player (Priya).
|
|
56
|
+
*/
|
|
57
|
+
export const coachOnEndLine = `Coach the player: start the ${coachAgent} agent in the background with the prompt "Coach.", whatever the player asks: it judges `
|
|
58
|
+
+ "the move that ended the game, apart from the analyst; when you start the analyst too, start both in one message. Start it even when an agent of the move "
|
|
59
|
+
+ "before has just ended, and write nothing about it in the chat, now or when it ends.";
|
|
49
60
|
/**
|
|
50
61
|
* How to start the agents, in one message, the one that thinks first, since it needs the time (Priya), and that the player sees nothing of
|
|
51
62
|
* them: their end needs no answer either. The end of the last move's agents, told just after, must not take their place (Priya).
|
|
@@ -61,18 +72,33 @@ export function agentsLine(agents) {
|
|
|
61
72
|
return `Think on the player's time and coach them: in one message, start two agents in the background, ${first} with ${prompt(first)} and ${second} with `
|
|
62
73
|
+ `${prompt(second)}; start them even when an agent of the move before has just ended, and write nothing about them in the chat, now or when they end.`;
|
|
63
74
|
}
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Whether a game is gone over already: reviewed, or taken by an analyst at work on it, whatever the page opened since (Federico); not once its review is
|
|
77
|
+
* deleted, for the analyst to write it again (Stefano).
|
|
78
|
+
*/
|
|
79
|
+
export function goneOver(context, game) {
|
|
80
|
+
if (analystOf(context, game) !== undefined) {
|
|
81
|
+
return true;
|
|
70
82
|
}
|
|
71
83
|
try {
|
|
72
|
-
return isReviewed(
|
|
84
|
+
return isReviewed(namedGame(context, game).record);
|
|
73
85
|
}
|
|
74
86
|
catch {
|
|
75
|
-
// A game
|
|
87
|
+
// A game that cannot be read is offered as it would be: the analyst says what is wrong with it.
|
|
76
88
|
return false;
|
|
77
89
|
}
|
|
78
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The run of the analyst agent at work on a game: the one that took it last, unless its chat has started again since, not only summarized, and took the
|
|
93
|
+
* agent with it.
|
|
94
|
+
*/
|
|
95
|
+
export function analystOf(context, game) {
|
|
96
|
+
const taken = gameInAnalysis(gameFolder(context.home()))?.analyst;
|
|
97
|
+
const start = lastChatStart(context.folder);
|
|
98
|
+
const gone = taken !== undefined && start !== undefined && start.source !== "compact" && Date.parse(start.at) > Date.parse(taken.since);
|
|
99
|
+
return taken?.game === game && !gone ? taken.run : undefined;
|
|
100
|
+
}
|
|
101
|
+
/** What stops an analyst agent on a game another analyst has taken: the chat hears of the review from that one (Priya). */
|
|
102
|
+
export const anotherAnalyst = "Stop now: another analyst is reviewing this game. End with one line for the AI that started you: its review is on its way to the "
|
|
103
|
+
+ "board page, and its end comes to you by itself; meanwhile talk the game over with the player whenever they ask. Should it never come, the player has "
|
|
104
|
+
+ "the game analysed again from the board page's list of analyses.";
|
package/dist/commands/ai-say.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { moods } from "../core/index.js";
|
|
3
3
|
import { archivedName, talkLimits } from "../games/index.js";
|
|
4
|
-
import { coachAgent } from "../host-files/index.js";
|
|
5
4
|
import { gameFolder } from "../profiles/index.js";
|
|
6
|
-
import {
|
|
5
|
+
import { analystLine, coachOnEnd, coachOnEndLine, reviewedLine, withAnalyst } from "./agents.js";
|
|
7
6
|
import { newGameStep, nextStep, wordLines } from "./answer-lines.js";
|
|
8
7
|
import { summaryOf } from "./game-summary.js";
|
|
9
8
|
import { refuseOtherGame } from "./play-move.js";
|
|
@@ -16,7 +15,7 @@ export const aiSay = defineCommand({
|
|
|
16
15
|
+ "something you have to say; when you are to move, speak in ai_move's comment. Between games, and before the first, it speaks freely: about the game just over, or to start another. "
|
|
17
16
|
+ 'Example: ai_say {text: "Sure, I\'ll play faster."}.',
|
|
18
17
|
input: z.object({
|
|
19
|
-
text: z.string().trim().min(1, "write what you say").max(talkLimits.aiHelps, `write ${talkLimits.aiHelps} characters at most`).describe(`your words for the player, one or two short sentences, ${talkLimits.ai} characters at most (${talkLimits.aiHelps} with the helps on): pieces and moves in `
|
|
18
|
+
text: z.string().trim().min(1, "write what you say").max(talkLimits.aiHelps, `write ${talkLimits.aiHelps} characters at most`).describe(`your words for the player, one or two short sentences, ${talkLimits.ai} characters at most (${talkLimits.aiHelps} with the helps on, or with no game going on): pieces and moves in `
|
|
20
19
|
+ `words ("your knight takes on f6"), a move you write with the pieces' figurines, as the tools show it (♞xf6+ for Nxf6+); your plans and threats only with the helps on; **two asterisks** put a word or two in bold on the page`),
|
|
21
20
|
mood: z.enum(moods).optional().describe("only after the game has ended: the face the board page keeps for its end"),
|
|
22
21
|
}),
|
|
@@ -36,7 +35,7 @@ export const aiSay = defineCommand({
|
|
|
36
35
|
format: ({ game, words, coach }) => [
|
|
37
36
|
"Said on the board page, where the player reads it: don't write it again in the chat.",
|
|
38
37
|
...wordLines(words),
|
|
39
|
-
...(coach === true ? [
|
|
38
|
+
...(coach === true ? [coachOnEndLine] : []),
|
|
40
39
|
afterWords(game, words),
|
|
41
40
|
].join("\n"),
|
|
42
41
|
});
|
|
@@ -13,6 +13,8 @@ interface MoveFacts {
|
|
|
13
13
|
material?: string;
|
|
14
14
|
/** After it, the pieces at risk of either side when they changed from the line before ("none" once no piece is), and the mates the side to move has; none once the board ends the game. */
|
|
15
15
|
atRisk?: string;
|
|
16
|
+
/** After it, the only legal move of the side to move, when it has one alone: a rule, as a mate is, never a judgment (D-103). */
|
|
17
|
+
oneLegal?: string;
|
|
16
18
|
mates?: readonly string[];
|
|
17
19
|
/** After it, what the moves of the side to move would do (D-100): forks, double attacks, discovered and double checks, promotions, stalemates and traps. */
|
|
18
20
|
tactics?: string;
|
|
@@ -33,6 +35,8 @@ interface Analysis {
|
|
|
33
35
|
instructions?: string;
|
|
34
36
|
/** The player's language, for the analyst's agent, which talks with no one (D-89). */
|
|
35
37
|
speech?: string;
|
|
38
|
+
/** Who the player is, how they play and their rating, for the analyst's agent, which never reads status (D-66). */
|
|
39
|
+
profile?: string;
|
|
36
40
|
white: string;
|
|
37
41
|
black: string;
|
|
38
42
|
/** In a game played here: the side the AI played, and its level. */
|