JavaScript Package (WebAssembly)
The CheckAI chess engine is available as a Bun/Node.js package compiled to WebAssembly. It runs at near-native speed and provides the full engine feature set — position analysis, game management, export, and board display.
Installation
The package is published on GitHub Packages under the @josunlp scope.
1. Configure the registry
echo "@josunlp:registry=https://npm.pkg.github.com" >> ~/.npmrc2. Install
# As a global CLI tool
bun add --global @josunlp/checkai
# As a project dependency
bun add @josunlp/checkaiCLI Usage
Once installed globally, the checkai command is available:
# Position analysis
checkai fen # Starting position FEN
checkai moves "<FEN>" # List legal moves
checkai eval "<FEN>" # Static evaluation (centipawns)
checkai search "<FEN>" --depth 15 # Best move search
checkai move "<FEN>" e2e4 # Apply a move
checkai board "<FEN>" # ASCII board diagram
# Game management
checkai game new # Create game (returns ID)
checkai game new "<FEN>" # Create from custom position
checkai game state <ID> # Full game state
checkai game move <ID> e2 e4 # Submit move
checkai game move <ID> e7 e8 Q # Submit with promotion
checkai game action <ID> resign # Resign
checkai game action <ID> offer_draw # Offer draw
checkai game action <ID> claim_draw threefold_repetition
checkai game list # List active games
checkai game delete <ID> # Delete game
# Export
checkai export <ID> pgn # PGN format
checkai export <ID> json # JSON format
checkai export <ID> text # Human-readable text
# Interactive play
checkai play
# Other
checkai version
checkai helpAdd --json to any command for JSON output.
Library API
import { engine } from "@josunlp/checkai";
// --- Position analysis ---
const fen = engine.startingFen();
const moves = engine.legalMoves(fen); // Array of legal moves
const score = engine.evaluate(fen); // Centipawns
const result = engine.bestMove(fen, 10); // { bestMove, score, depth, pv, nodes, timeMs }
const after = engine.makeMove(fen, "e2e4"); // { fen, isCheck, isCheckmate, ... }
engine.isCheckmate(fen); // boolean
engine.isStalemate(fen); // boolean
engine.isCheck(fen); // boolean
engine.isInsufficientMaterial(fen); // boolean
// --- Board display ---
console.log(engine.boardToAscii(fen));
// --- Game management ---
const gameId = engine.createGame();
// const gameId = engine.createGameFromFen("custom FEN");
let state = engine.gameSubmitMove(gameId, "e2", "e4");
state = engine.gameSubmitMove(gameId, "e7", "e5");
console.log(state.fen, state.turn, state.isCheck);
state = engine.gameState(gameId);
const history = engine.gameMoveHistory(gameId);
const currentFen = engine.gameFen(gameId);
engine.gameProcessAction(gameId, "resign");
engine.listGames();
engine.deleteGame(gameId);
// --- Export ---
const pgn = engine.gameToPgn(gameId);
const json = engine.gameToJson(gameId);
const text = engine.gameToText(gameId);API Reference
Position Analysis
| Function | Parameters | Returns |
|---|---|---|
startingFen() | — | string |
legalMoves(fen) | FEN string | Array<{ from, to, promotion?, notation }> |
evaluate(fen) | FEN string | number (centipawns) |
bestMove(fen, depth) | FEN, depth 1–128 | { bestMove, score, depth, seldepth, mateIn, pv, lines, nodes, nps, timeMs, hashfull } |
analyze(fen, options) | FEN, { depth, movetime, nodes, multiPv, hashMb, skillLevel } | the same object, with lines holding the best N variations |
engineInfo() | — | { name, version, maxDepth, maxMultiPv, maxSkill, threadsSupported, features } |
makeMove(fen, move) | FEN, move (e.g. "e2e4") | { fen, isCheck, isCheckmate, isStalemate, isInsufficientMaterial } |
isCheckmate(fen) | FEN string | boolean |
isStalemate(fen) | FEN string | boolean |
isCheck(fen) | FEN string | boolean |
isInsufficientMaterial(fen) | FEN string | boolean |
boardToAscii(fen) | FEN string | string |
Game Management
| Function | Parameters | Returns |
|---|---|---|
createGame() | — | string (ID) |
createGameFromFen(fen) | FEN string | string (ID) |
gameState(id) | game ID | Game state |
gameSubmitMove(id, from, to, promotion?) | game ID, squares, optional promo | Game state |
gameProcessAction(id, action, reason?) | game ID, action string | Game state |
gameMoveHistory(id) | game ID | Array |
gameFen(id) | game ID | string |
deleteGame(id) | game ID | void |
listGames() | — | string[] |
Export
| Function | Parameters | Returns |
|---|---|---|
gameToPgn(id) | game ID | string |
gameToJson(id) | game ID | string |
gameToText(id) | game ID | string |
Engine parity
Since 1.0.0 the WebAssembly build compiles the same search source as the native binary: Principal Variation Search with singular extensions, continuation history, a lock-free transposition table, MultiPV and skill limiting. The only difference is threading — WebAssembly has no worker threads, so analyze always searches on one thread and engineInfo().threadsSupported reports false.
How It Works
The engine is written in Rust and compiled to WebAssembly using wasm-pack. The WASM crate (wasm/) re-uses the core Rust source files via #[path] directives — there is zero code duplication between the native and WASM builds.
The search uses iterative deepening with aspiration windows, Principal Variation Search (PVS), a 16 MB transposition table, null-move pruning, Late Move Reductions, killer/history/counter-move heuristics, quiescence search, and SEE-based pruning.
Requirements
- Node.js ≥ 18 (WASM support required)
- GitHub Packages authentication (for
bun add)