Games

Game skill

Install the Xara game skill once, then describe a game and your coding assistant builds it.

Install the Xara game skill once. After that, describe the game you want and your coding assistant builds it with the Xara game SDK.

A skill is a file of instructions the assistant reads before it writes code. This one tells Cursor, Claude Code, GitHub Copilot, and Codex how a Xara chat game works: turns, the move list, the line on the chat card, and a purchase when the game sells something.

You install it once per game project. Then describe the game in your own words, for example "two players guess a number, and a hint costs ₦100".

You don't have to install it. Open the skill file, copy all of it, and paste it at the start of the chat before you describe the game.

What the skill does

JobWhat your assistant builds
The gameA static web page that connects with @xavatech/xara-game-sdk
The rulesRules in their own file, with tests, replayed from the move list on every phone
A purchaseOnly when your description includes something to sell, approved with the player's PIN in the Xara app

Install

A game started with npx @xavatech/create-xara-game my-game already has the skill, in .cursor/skills/. For an existing project, run the command for your tool from its root folder.

Cursor, Claude Code, and GitHub Copilot

curl -fsSL --create-dirs -o .claude/skills/build-xara-game/SKILL.md https://docs.usexara.ai/skills/build-xara-game/SKILL.md

Cursor also reads .cursor/skills/. Use that path if this project is only opened in Cursor:

curl -fsSL --create-dirs -o .cursor/skills/build-xara-game/SKILL.md https://docs.usexara.ai/skills/build-xara-game/SKILL.md

Codex

curl -fsSL --create-dirs -o .agents/skills/build-xara-game/SKILL.md https://docs.usexara.ai/skills/build-xara-game/SKILL.md

On Windows, run the commands in PowerShell with curl.exe.

Use it

Describe the game. The assistant loads the skill when the task is a game for Xara.

A two-player game. Players take turns naming a letter. The first to complete a four-letter word wins.
Four players. On your turn you roll a die and add it to your score. First to 20 wins. A second roll on the same turn costs ₦50.

Update

Run the install command again. It replaces the file.

What the skill says

The skill is plain Markdown. This is the file the install command saves, SKILL.md:

---
name: build-xara-game
description: >-
  Builds a turn-based web game for Xara chats from a plain-language game
  description, using @xavatech/xara-game-sdk. Use when a third party asks to
  create, scaffold, or implement a game for Xara, describes a game people
  should play from a Xara chat, or mentions the Xara game SDK.
---

# Build a Xara game

The user describes a game. Build it as its own static web page with `@xavatech/xara-game-sdk`. Create a new project. Do not add the game to the SDK repo, and do not call Xara's HTTP API.

Xara already checks membership, acceptance, whose turn it is, and that a move follows the previous one. The game checks whether a move is legal.

## Turn the description into rules

Write this down before the screen:

- Player count, fixed or a range, between 2 and 8. The starter goes first. Turns continue in that order.
- One move, as JSON the game sends. Keep it under 4,000 characters.
- How replaying `state.moves` draws the current game. Every phone, including someone watching, rebuilds from that list. Do not keep board state only in memory.
- Who wins, and what a draw is.
- A purchase, only if the description includes one: the item name, a whole naira amount from 1 to 1,000,000, and a reference.

A fact every phone must share, and that is not itself a move (a secret number, a shuffled deck), is derived from `session.id` plus the move list. There is no hidden game state on the server.

The chat card shows one summary to everyone. It never says "you". Name the player by their mark, the winning value, or similar. Stay within 120 characters.

## Project

Vite and TypeScript. `base: "./"` so the built files work from any https path. One private package. Depend on `@xavatech/xara-game-sdk`.

```
index.html
src/main.ts
src/rules.ts
src/rules.test.ts
src/style.css
vite.config.ts
```

Rules live in `src/rules.ts` and have tests. `src/main.ts` only draws state and sends moves. Do not add a helper module for something `rules.ts` can do.

Lock the page to itself:

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'none'">
```

No other origins, no CDN fonts, no iframes.

## Connect

```ts
import { connect, inXara, mockApp } from "@xavatech/xara-game-sdk";

if (!inXara()) {
  mockApp({
    players: [
      { id: 1, name: "Ada" },
      { id: 2, name: "Bayo" },
    ],
    approvePayment: (item, amount) => window.confirm(`Buy ${item} for ₦${amount}?`),
  });
}

type Move = { cell: number };
const xara = connect<Move>();
xara.onChange(draw);
draw(await xara.load());
```

`mockApp` runs only outside the app. It adds a bar that switches which player's phone the page is, and a move only goes through on that player's turn. Inside the app it never runs.

## Draw and play

Redraw from the `GameState` passed to `draw`. Fields the game uses:

- `session.status`: `invited`, `active`, `completed`, `declined`, `cancelled`
- `session.moveCount`, `session.turn`, `session.id`, `session.outcome` (`winners`, `summary`)
- `players`: `id`, `name`, `status`, `you`
- `me`: `id`, `watching` (a group member who is not playing)
- `moves`: `step`, `playerId`, `move`, `at` — oldest first. `move` is the JSON this game sent
- `purchases`: `reference`, `status` (`requested` or `paid`), `receipt`

While `session.status` is `invited` and this phone's player is still invited, show an accept control that calls `xara.accept()`. The session starts after every invitee accepts. Someone watching gets no accept control and no move control.

Accept a move only when `xara.isMyTurn()` is true. Ignore clicks otherwise.

```ts
await xara.move({ cell }, won
  ? { outcome: { winners: [xara.state!.me.id], summary: "X wins with three in a row" } }
  : {});
```

Pass `outcome` on the move that ends the game. `winners` is the player ids. An empty list is a draw. Call `xara.end(outcome)` only when the game ends without a move. `xara.close()` leaves the game screen.

Give status, the board, and any purchase line a fixed height so a new move does not push the page around.

## A purchase

Skip this when the description has nothing to sell.

```ts
const paid = await xara.buy({
  item: "Hint",
  amount: 100,
  reference: `hint-${state.session.id}-${state.session.moveCount}`,
});
if (paid.status === "success") showHint();
```

The reference matches `/^[A-Za-z0-9_.:-]{1,64}$/`. The same reference returns the existing purchase. A different item or amount on that reference is refused. Put `moveCount` in the reference only when a later turn is a new purchase; leave it out when the purchase is once per session (`hint-${session.id}`).

After a reload, read `state.purchases`. Show the item when one with that reference has `status === "paid"`. The page never asks for a PIN. The app shows its own approval sheet. The payee is the business that submitted the game.

`XaraGameError` codes are `rejected`, `timeout`, and `closed`. A closed error means the page was not opened from a Xara chat.

## Names to use

Use the names above. These older names are not the SDK: `ready`, `onState`, `viewer`, `requestPayment`, `installMockHost`, `winner_ids`, `revision`, `turn_user_id`.

## Check

1. `npm test` for the rules, including an illegal move, a win, a draw, and a purchase reference if the game sells something.
2. `npm run build`.
3. With the dev server, play a full game through `mockApp`: accept, take turns, reach the outcome, and buy once if the game sells something. Play again with the same reference and confirm it does not describe a second charge.
4. Replay the same `moves` and confirm both players would draw the same board.

## What to tell the user

The built `dist/` is a static site. It has to be served over https on one origin. They submit that URL in the Xara business dashboard with the player counts and SDK version `2.1.0`. The game stays in review until an admin approves it. Changing the URL or the player counts after approval sends it back to review. Changing the name, description, or icon does not.

Do not submit, host, or publish the package unless the user asks.
Copyright © 2026