@leafarcade/cli 0.1.0 → 0.1.1
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 +39 -10
- package/dist/cli.mjs +79 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Let your own agent play a private board-game room while you use the browser board. The CLI reads positions and submits moves; it does not call a model, collect model credentials or implement its own opponent.
|
|
4
4
|
|
|
5
|
-
**Scope of 0.1.
|
|
5
|
+
**Scope of 0.1.1:** all 47 multiplayer games in private, unclocked, unrated rooms. Nine games expose complete local legal-action lists; the others expose player-visible state and game-specific JSON action formats, validated by the game service. OAuth login, seat-scoped pairing and automatic waking of a stopped agent are not implemented. Anyone with an invite can currently occupy an open seat under the existing private-room rules. Use private invites you trust.
|
|
6
6
|
|
|
7
7
|
## Start a game from your agent
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npx @leafarcade/cli@0.1.
|
|
10
|
+
npx @leafarcade/cli@0.1.1 game create --game connect-four --name "My agent" --open
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
This creates a private, unrated room with the CLI agent as host, saves its session, and opens the human's browser invitation. The human joins with their own name and session. Once the requested players are present, the agent reads status and starts with the latest revision:
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
npx @leafarcade/cli@0.1.
|
|
17
|
-
npx @leafarcade/cli@0.1.
|
|
16
|
+
npx @leafarcade/cli@0.1.1 game status --room ROOMCODE
|
|
17
|
+
npx @leafarcade/cli@0.1.1 game start --room ROOMCODE --revision R
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Omit `--open` on a remote or headless machine and share the returned URL. If launching the browser fails, the room is still valid: open that URL manually or use `leafarcade game open --room ROOMCODE`. No agent credential is placed in the URL. `--capacity N` selects a supported seat count (default: the game's minimum); Go also supports `--go-size 9|13|19`. For a custom service, provide both `--server` and its matching browser frontend `--site`.
|
|
@@ -24,27 +24,27 @@ The CLI stores a persistent creator identity so creating another room does not r
|
|
|
24
24
|
## Install the agent skill
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
npx @leafarcade/cli@0.1.
|
|
28
|
-
npx @leafarcade/cli@0.1.
|
|
27
|
+
npx @leafarcade/cli@0.1.1 skill show
|
|
28
|
+
npx @leafarcade/cli@0.1.1 skill install --agent codex
|
|
29
29
|
# Or:
|
|
30
|
-
npx @leafarcade/cli@0.1.
|
|
30
|
+
npx @leafarcade/cli@0.1.1 skill install --agent claude
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The skill is bundled in the npm package and works without a separate download. Installation is explicit; there is no npm postinstall hook. Codex installs into `$CODEX_HOME/skills/leafarcade` (default `~/.codex/skills/leafarcade`); Claude uses `~/.claude/skills/leafarcade`. Existing customized skills are preserved. Refresh your agent's skills and ask it to play Leaf Arcade. The [public skill repository](https://github.com/leafarcade/skills) contains usage instructions; the CLI source repository remains private. The npm JavaScript executable is publicly downloadable.
|
|
34
34
|
|
|
35
35
|
## Install
|
|
36
36
|
|
|
37
|
-
Requires Node.js 24 or later. Release 0.1.
|
|
37
|
+
Requires Node.js 24 or later. Release 0.1.1 includes room creation, browser opening, host start and bundled skill installation:
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
npm install --global @leafarcade/cli@0.1.
|
|
40
|
+
npm install --global @leafarcade/cli@0.1.1
|
|
41
41
|
leafarcade --help
|
|
42
42
|
leafarcade help games
|
|
43
43
|
leafarcade help chess
|
|
44
44
|
leafarcade game play --help --game chess
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Without a global install, use `npx @leafarcade/cli@0.1.
|
|
47
|
+
Without a global install, use `npx @leafarcade/cli@0.1.1 ...` in place of `leafarcade ...`. From the repository, run `npm run build --prefix packages/cli` and replace `leafarcade` with `node packages/cli/dist/cli.mjs`. The installed package has no runtime dependencies or install scripts.
|
|
48
48
|
|
|
49
49
|
## First game
|
|
50
50
|
|
|
@@ -133,3 +133,32 @@ If you accidentally join an unsupported room, the service may already have reser
|
|
|
133
133
|
This uses your agent's normal command tools. It does not turn a ChatGPT/Claude subscription into an API key or access their credential stores. Check your client/workspace permissions before use.
|
|
134
134
|
|
|
135
135
|
MIT license covers this CLI package. Bundled chess.js is BSD-2-Clause; its full notice is included in THIRD_PARTY_NOTICES.txt. No game artwork, proprietary resource pack, account secret or third-party engine binary is included.
|
|
136
|
+
|
|
137
|
+
## Public tables (next local release)
|
|
138
|
+
|
|
139
|
+
Find public seats that admit external agents:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
leafarcade game tables --game chess
|
|
143
|
+
leafarcade join ROOMCODE --name "My agent"
|
|
144
|
+
leafarcade game create --game chess --access public --name "My agent"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`--access invite` is the default: unlisted, accessible by link/code. `--access
|
|
148
|
+
private` requires host approval for new players and spectators. Public tables are
|
|
149
|
+
listed for discovery. New CLI-created tables allow spectators; use
|
|
150
|
+
`--no-spectators` to disable watching. The browser's Watch action never occupies a
|
|
151
|
+
seat and hides all private player information.
|
|
152
|
+
|
|
153
|
+
A pending private join returns `access_requested`; repeat `join` after approval,
|
|
154
|
+
using the saved identity. Hosts inspect `access_requests` in `game status` and
|
|
155
|
+
approve or decline the requested person and role:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
leafarcade game approve --room ROOMCODE --revision R --request REQUEST_ID
|
|
159
|
+
leafarcade game decline --room ROOMCODE --revision R --request REQUEST_ID
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Discovery does not automatically join, play, poll or wake an agent. These local
|
|
163
|
+
commands require the matching upcoming service release; the currently published
|
|
164
|
+
npm version is not changed by this source work.
|
package/dist/cli.mjs
CHANGED
|
@@ -3576,6 +3576,10 @@ import { randomUUID } from "node:crypto";
|
|
|
3576
3576
|
|
|
3577
3577
|
// lib/multiplayer/ids.ts
|
|
3578
3578
|
var multiplayerIds = [
|
|
3579
|
+
"categories",
|
|
3580
|
+
"cheat",
|
|
3581
|
+
"president",
|
|
3582
|
+
"rummy",
|
|
3579
3583
|
"tic-tac-toe",
|
|
3580
3584
|
"connect-four",
|
|
3581
3585
|
"dots-boxes",
|
|
@@ -3621,6 +3625,9 @@ var multiplayerIds = [
|
|
|
3621
3625
|
"baska"
|
|
3622
3626
|
];
|
|
3623
3627
|
function seatLimits(id) {
|
|
3628
|
+
if (id === "categories") return [2, 8];
|
|
3629
|
+
if (id === "cheat" || id === "president") return [3, 6];
|
|
3630
|
+
if (id === "rummy") return [2, 4];
|
|
3624
3631
|
if (id === "draw-guess") return [2, 20];
|
|
3625
3632
|
if (id === "three-five-eight") return [3, 3];
|
|
3626
3633
|
if (id === "bridge" || id === "binokel" || id === "baska") return [4, 4];
|
|
@@ -3732,6 +3739,10 @@ function owareMoves(s) {
|
|
|
3732
3739
|
|
|
3733
3740
|
// lib/multiplayer/catalog.ts
|
|
3734
3741
|
var multiplayerGames = [
|
|
3742
|
+
{ "id": "categories", "name": "Categories", "symbol": "A\u2013Z", "players": "2\u20138 players", "description": "One letter. Six categories. Race your friends to a unique answer.", "rules": "Five English-language rounds with Country, City, Animal, Food, Occupation and Object. The host reveals a new random letter and starts a two-minute round, or chooses untimed play. Submit one locked answer sheet; blank answers are allowed. A complete sheet can call Stop once, shortening a timed round to ten seconds. After everyone submits or a player reveals an expired round, all answers are shown. Unsubmitted answers are blank. Each player casts one ballot marking invalid answers from other players; an answer needs a strict majority of the other players to accept it (a voting tie rejects it). Valid unique answers score 10, matching valid answers 5, blanks or rejected answers 0. Highest total after five rounds wins; equal totals draw. Private groups judge answers themselves; there is no automated dictionary or AI judge. Local play uses untimed private handoffs." },
|
|
3743
|
+
{ "id": "cheat", "name": "Cheat", "symbol": "?!", "players": "3\u20136 players", "description": "Play face down. Tell a convincing story. Call the bluff.", "rules": "Deal all 52 cards clockwise. Player 1 claims aces, then claims cycle through 2 to king and back to ace. On your turn place one to four cards face down, claiming the required rank; bluffing is allowed. Before the next play, any other player may challenge, or the next player may accept. A challenge reveals only the latest cards: if any is the wrong rank the claimant takes the whole pile; otherwise the challenger takes it. Accepted cards stay hidden. The next clockwise player then takes their turn with the next required rank. An empty hand wins only after its final claim is accepted or survives a challenge. This table uses an explicit challenge window without a reaction timer." },
|
|
3744
|
+
{ "id": "president", "name": "President", "symbol": "3\u21922", "players": "3\u20136 players", "description": "Beat the group. Pass at the right moment. Finish first.", "rules": "A single deal with a standard 52-card deck, all cards dealt clockwise. Ranks rise from 3 through ace, with 2 highest. The holder of 3 of clubs opens with a group containing it. Lead one to four equal-rank cards; following plays must contain the same number of cards at a strictly higher rank. You may pass, but cannot re-enter until that pile clears. When all other active players pass, the last player to play leads again; if they have finished, the next active player leads. Finishers leave play and receive places in order. Play continues until one player remains; the first finisher is President. No jokers, suit tie-breaks, special twos, revolution or between-deal tribute." },
|
|
3745
|
+
{ "id": "rummy", "name": "Classic Rummy", "symbol": "777", "players": "2\u20134 players", "description": "Draw a card. Build sets and runs. Empty your hand.", "rules": "One deal, no jokers. Two players receive ten cards each; three or four receive seven. Aces are low only. Draw one card from the stock or take the top discard. Then optionally meld sets of three or four equal ranks, or runs of three or more consecutive cards of one suit. You may add held cards to either end of any public run or complete a public set. Keep at least one card and end your turn by discarding; a taken upcard cannot be discarded immediately. First to discard their last card wins and scores the total value of cards held by opponents: ace 1, faces 10, other cards face value. Recycle discards beneath the top card when stock runs out. If stock and recyclable discards are both empty and the next player chooses stock, the deal ends; lowest remaining card value wins, with ties drawn. No initial meld threshold, knock or Rummy 500 scoring." },
|
|
3735
3746
|
{ id: "tic-tac-toe", name: "Tic-tac-toe", symbol: "XO", players: "2 players", description: "Nine squares. Three in a row. Your next quick match.", rules: "X starts on a 3 by 3 board. Take turns placing your mark on an empty square. Three marks in a horizontal, vertical or diagonal line wins. A full board without a winner draws." },
|
|
3736
3747
|
{ id: "international-draughts", name: "International Draughts", symbol: "\u265B", players: "2 players", description: "Twenty pieces. Flying kings. Capture the longest chain.", rules: "White starts on a 10\xD710 board with twenty pieces per side. Men move one diagonal square forward and capture in either direction. Kings slide diagonally and land on any empty square beyond a captured piece. Captures are compulsory: choose a sequence taking the greatest number of pieces. Captured pieces remain as blockers until the entire sequence ends. A man becomes a king only if its final square is on the promotion row. No pieces or no legal move loses. Threefold repetition, twenty-five successive king moves per player without captures or man moves, and the official sixteen/five-move king endgame limits draw automatically." },
|
|
3737
3748
|
{ id: "atomic-chess", name: "Atomic Chess", symbol: "\u265C", players: "2 players", description: "Every capture explodes. Protect your king from the blast.", rules: "Standard chess setup and moves, with explosive captures: the capturing piece, captured piece and adjacent non-pawns disappear. Adjacent pawns survive. En passant explodes at the capturing pawn\u2019s destination. A move that explodes your own king is illegal. Kings cannot capture, but may stand next to one another. Explode the opposing king or deliver checkmate to win. Stalemate, insufficient material, threefold repetition or fifty moves without a pawn move or capture draws. Castling and all four promotions are supported." },
|
|
@@ -4285,11 +4296,39 @@ var action_catalog_default = {
|
|
|
4285
4296
|
],
|
|
4286
4297
|
"brazilian-checkers": [
|
|
4287
4298
|
"move"
|
|
4299
|
+
],
|
|
4300
|
+
categories: [
|
|
4301
|
+
"begin",
|
|
4302
|
+
"submit",
|
|
4303
|
+
"stop",
|
|
4304
|
+
"expire",
|
|
4305
|
+
"vote",
|
|
4306
|
+
"next"
|
|
4307
|
+
],
|
|
4308
|
+
cheat: [
|
|
4309
|
+
"play",
|
|
4310
|
+
"accept",
|
|
4311
|
+
"challenge"
|
|
4312
|
+
],
|
|
4313
|
+
president: [
|
|
4314
|
+
"play",
|
|
4315
|
+
"pass"
|
|
4316
|
+
],
|
|
4317
|
+
rummy: [
|
|
4318
|
+
"stock",
|
|
4319
|
+
"upcard",
|
|
4320
|
+
"meld",
|
|
4321
|
+
"layoff",
|
|
4322
|
+
"discard"
|
|
4288
4323
|
]
|
|
4289
4324
|
};
|
|
4290
4325
|
|
|
4291
4326
|
// lib/agents/action-help.ts
|
|
4292
4327
|
var help = {
|
|
4328
|
+
categories: "begin: host starts a timed round; promotion:'untimed' disables the timer. submit/stop: text is a JSON array of six English answers in category order; stop needs a complete sheet. expire reveals a timed-out round. vote: cards is rejected cell IDs (owner seat * 6 + category index), excluding your own row; an empty array accepts all other answers. next: host advances. Answers and ballots lock once submitted. Include at: the visible zero-based round and scoreKey: the visible roundKey on submit/stop/vote/expire to allow independent same-round actions arriving on the same revision.",
|
|
4329
|
+
cheat: "play: cards is one to four card IDs from your hand, claiming the displayed rank. During challenge phase any other seat may challenge; only the next player may accept. Final-card claims must resolve before a win.",
|
|
4330
|
+
president: "play: cards is one to four equal-rank card IDs. Match pile size and beat its rank; 2 is high. Opening group includes 3 of clubs. pass sits out until the pile clears. Finished seats leave play; order gives final places.",
|
|
4331
|
+
rummy: "stock/upcard: draw first. meld: cards is a set or run of held card IDs. layoff: at is a public meld index, cards is held card IDs to add. discard: at is a held card ID. Aces low; keep one final discard; do not immediately discard the upcard you took.",
|
|
4293
4332
|
"connect-four": "place: at is a zero-based column (0\u20136). Convenience: --column 1\u20137.",
|
|
4294
4333
|
"tic-tac-toe": "place: at is a zero-based row-major square (0\u20138).",
|
|
4295
4334
|
chess: "move: from/to are algebraic squares; promotion is q/r/b/n.",
|
|
@@ -4395,6 +4434,8 @@ function agentPosition(room) {
|
|
|
4395
4434
|
game: room.game,
|
|
4396
4435
|
revision: room.revision,
|
|
4397
4436
|
phase: room.phase,
|
|
4437
|
+
access: room.access ?? "invite",
|
|
4438
|
+
...room.requests ? { access_requests: room.requests } : {},
|
|
4398
4439
|
you: { seat: room.seat, piece: !complete ? `seat-${room.seat + 1}` : room.game === "chess" ? room.seat === 0 ? "white" : "black" : room.seat === 0 ? "X" : "O", left: room.players[room.seat].left },
|
|
4399
4440
|
turn: match && room.phase === "playing" ? { seat: turn, yours } : null,
|
|
4400
4441
|
players: room.players.map((p, seat) => ({ seat, name: p.name, left: p.left, ...p.agent ? { agent: true } : {}, ...p.paused ? { paused: true } : {} })),
|
|
@@ -4548,7 +4589,7 @@ async function rpc(session, method, payload = {}, query = false, signal) {
|
|
|
4548
4589
|
redirect: "error",
|
|
4549
4590
|
credentials: "omit",
|
|
4550
4591
|
headers: { "Content-Type": "application/json" },
|
|
4551
|
-
body: JSON.stringify({ type: query ? "query" : "mutation", path: `multiplayer:${method}`, args: { token: session.token, ...["register", "create"].includes(method) ? {} : { code: session.room }, ...payload } }),
|
|
4592
|
+
body: JSON.stringify({ type: query ? "query" : "mutation", path: `multiplayer:${method}`, args: { token: session.token, ...["register", "create", "list"].includes(method) ? {} : { code: session.room }, ...payload } }),
|
|
4552
4593
|
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
|
|
4553
4594
|
});
|
|
4554
4595
|
} catch {
|
|
@@ -4665,12 +4706,12 @@ async function createRoom(options, signal) {
|
|
|
4665
4706
|
const pendingPath = sessionPath(options.server, "creating");
|
|
4666
4707
|
let pending = await loadSession(pendingPath);
|
|
4667
4708
|
if (pending) {
|
|
4668
|
-
if (pending.server !== options.server || pending.token !== creator.token || !pending.creation || pending.creation.game !== options.game || pending.creation.capacity !== capacity || pending.creation.goSize !== options.goSize) throw Error("An unfinished creation is saved. Resume it with the original game/capacity/board size before creating another room.");
|
|
4709
|
+
if (pending.server !== options.server || pending.token !== creator.token || !pending.creation || pending.creation.game !== options.game || pending.creation.capacity !== capacity || pending.creation.goSize !== options.goSize || (pending.creation.access ?? "invite") !== (options.access ?? "invite") || (pending.creation.allowSpectators ?? true) !== (options.allowSpectators ?? true)) throw Error("An unfinished creation is saved. Resume it with the original game/capacity/board size before creating another room.");
|
|
4669
4710
|
} else {
|
|
4670
|
-
pending = await storeSession(pendingPath, { ...creator, name: name.trim(), creation: { requestId: randomUUID(), game: options.game, capacity, goSize: options.goSize } });
|
|
4711
|
+
pending = await storeSession(pendingPath, { ...creator, name: name.trim(), creation: { requestId: randomUUID(), game: options.game, capacity, goSize: options.goSize, access: options.access ?? "invite", allowSpectators: options.allowSpectators ?? true } });
|
|
4671
4712
|
}
|
|
4672
4713
|
await rpc(pending, "register", { name: pending.name }, false, signal);
|
|
4673
|
-
const reply = await rpc(pending, "create", pending.creation, false, signal);
|
|
4714
|
+
const reply = await rpc(pending, "create", { ...pending.creation, agent: true }, false, signal);
|
|
4674
4715
|
if (typeof reply?.code !== "string") throw Error("The room service returned an invalid room code. Your saved creation is retained.");
|
|
4675
4716
|
const code = roomCode(reply.code);
|
|
4676
4717
|
const path = sessionPath(options.server, code);
|
|
@@ -4732,9 +4773,9 @@ async function installSkill(agent = "codex") {
|
|
|
4732
4773
|
// packages/cli/package.json
|
|
4733
4774
|
var package_default = {
|
|
4734
4775
|
name: "@leafarcade/cli",
|
|
4735
|
-
version: "0.1.
|
|
4776
|
+
version: "0.1.1",
|
|
4736
4777
|
private: false,
|
|
4737
|
-
description: "Create private browser board games and play with your AI agent. Includes
|
|
4778
|
+
description: "Create private browser board games and play with your AI agent. Includes 47 games and an installable Codex/Claude skill.",
|
|
4738
4779
|
type: "module",
|
|
4739
4780
|
bin: {
|
|
4740
4781
|
leafarcade: "dist/cli.mjs"
|
|
@@ -4779,7 +4820,10 @@ var package_default = {
|
|
|
4779
4820
|
// packages/cli/src/help.ts
|
|
4780
4821
|
var agentCliVersion = package_default.version;
|
|
4781
4822
|
var commands = {
|
|
4782
|
-
|
|
4823
|
+
approve: { usage: "game approve --room CODE --revision R --request ID", description: "As host, approve a player or spectator access request shown by status. Approve only the requested participant." },
|
|
4824
|
+
decline: { usage: "game decline --room CODE --revision R --request ID", description: "As host, decline a pending access request." },
|
|
4825
|
+
tables: { usage: "game tables [--game GAME_ID]", description: "Find public tables with an open seat that accept AI agents. No automatic joining or polling." },
|
|
4826
|
+
create: { usage: 'game create --game connect-four --name "My agent" [--open] [--capacity N]', description: "Create a table as the agent host (--access public|invite|private; default invite) and return the human browser link. --open launches the browser. Saved creator identity preserves limits across rooms; uncertain creation can be resumed." },
|
|
4783
4827
|
open: { usage: "game open --room ROOMCODE", description: "Reopen the human browser link for a saved room. Does not create another game or share the agent credential." },
|
|
4784
4828
|
start: { usage: "game start --room ROOMCODE --revision R", description: "Start a room you host once the requested players have joined. Other seats cannot start it." },
|
|
4785
4829
|
skill: { usage: "skill install --agent codex|claude", description: "Install the bundled Leaf Arcade skill into your chosen agent user directory. Explicit command only; never runs during npm install. Existing customized skills are preserved. Use skill show to inspect it first." },
|
|
@@ -4797,6 +4841,10 @@ var commands = {
|
|
|
4797
4841
|
forget: { usage: "forget --room ROOMCODE", description: "Delete local access only. Does not resign or revoke a credential copied elsewhere. Use game leave to leave the room." }
|
|
4798
4842
|
};
|
|
4799
4843
|
var examples = {
|
|
4844
|
+
categories: '{"type":"begin","promotion":"untimed"}',
|
|
4845
|
+
cheat: '{"type":"play","cards":[0]}',
|
|
4846
|
+
president: '{"type":"play","cards":[1]}',
|
|
4847
|
+
rummy: '{"type":"stock"}',
|
|
4800
4848
|
"connect-four": "--column 4",
|
|
4801
4849
|
chess: "--from e2 --to e4",
|
|
4802
4850
|
checkers: "--from b6 --to a5",
|
|
@@ -4859,7 +4907,7 @@ Usage
|
|
|
4859
4907
|
leafarcade ${entry.usage}
|
|
4860
4908
|
` : `
|
|
4861
4909
|
Your agent plays through the terminal; you play in the browser.
|
|
4862
|
-
No model API key is collected.
|
|
4910
|
+
No model API key is collected. Unrated, unclocked tables that admit AI agents.
|
|
4863
4911
|
|
|
4864
4912
|
Usage
|
|
4865
4913
|
leafarcade COMMAND [OPTIONS]
|
|
@@ -4925,7 +4973,9 @@ Shared options
|
|
|
4925
4973
|
--capacity N Supported seat capacity (default: game minimum)
|
|
4926
4974
|
--go-size N Go board size: 9, 13 or 19
|
|
4927
4975
|
--agent codex|claude Skill installation target
|
|
4928
|
-
--
|
|
4976
|
+
--access TYPE public, invite (default), or private with host approval
|
|
4977
|
+
--no-spectators Disable observation when creating a table
|
|
4978
|
+
--room CODE Room code
|
|
4929
4979
|
--revision R Exact latest revision for mutations
|
|
4930
4980
|
--server URL Service origin; HTTPS, or localhost HTTP
|
|
4931
4981
|
--json JSON is already the default for commands
|
|
@@ -4964,6 +5014,9 @@ process.once("SIGINT", () => controller.abort());
|
|
|
4964
5014
|
process.once("SIGTERM", () => controller.abort());
|
|
4965
5015
|
try {
|
|
4966
5016
|
const { values, positionals } = parseArgs({ allowPositionals: true, options: {
|
|
5017
|
+
request: { type: "string" },
|
|
5018
|
+
access: { type: "string" },
|
|
5019
|
+
"no-spectators": { type: "boolean" },
|
|
4967
5020
|
open: { type: "boolean" },
|
|
4968
5021
|
site: { type: "string" },
|
|
4969
5022
|
capacity: { type: "string" },
|
|
@@ -4997,19 +5050,27 @@ try {
|
|
|
4997
5050
|
process.stdout.write(agentCliVersion2 + "\n");
|
|
4998
5051
|
} else {
|
|
4999
5052
|
const command = positionals[0] === "game" ? positionals[1] : positionals[0];
|
|
5000
|
-
if (!["create", "open", "start", "skill", "join", "rules", "status", "legal-actions", "play", "wait", "leave", "resign", "rematch", "pause", "resume", "forget"].includes(command)) throw Error("Unknown command. Run leafarcade --help.");
|
|
5053
|
+
if (!["approve", "decline", "tables", "create", "open", "start", "skill", "join", "rules", "status", "legal-actions", "play", "wait", "leave", "resign", "rematch", "pause", "resume", "forget"].includes(command)) throw Error("Unknown command. Run leafarcade --help.");
|
|
5001
5054
|
if (positionals.length > (positionals[0] === "game" || command === "join" || command === "skill" ? 2 : 1)) throw Error("Unexpected positional arguments.");
|
|
5002
5055
|
let result;
|
|
5003
5056
|
if (command === "skill") {
|
|
5004
5057
|
if (positionals[1] === "install") result = await installSkill(values.agent);
|
|
5005
5058
|
else if (positionals[1] === "show") result = { name: "leafarcade", files: await bundledSkill() };
|
|
5006
5059
|
else throw Error("Use leafarcade skill install --agent codex|claude, or leafarcade skill show.");
|
|
5060
|
+
} else if (command === "tables") {
|
|
5061
|
+
const server = serviceUrl(values.server ?? "https://leafarcade-multiplayer.slawomir-radzyminski.workers.dev");
|
|
5062
|
+
if (values.game && !supportsAgent(values.game)) throw Error("Unknown game ID.");
|
|
5063
|
+
const creatorPath = sessionPath(server, "creator");
|
|
5064
|
+
const session = await loadSession(creatorPath) ?? await createSession(creatorPath, server, "", "CLI Agent");
|
|
5065
|
+
const tables = await rpc(session, "list", values.game ? { game: values.game } : {}, true, controller.signal);
|
|
5066
|
+
result = { event: "tables", tables: tables.filter((table) => table.allowAgents && !table.timeControl && table.phase === "waiting" && table.players.length < table.capacity), next: "Choose a table, then leafarcade join ROOMCODE --name NAME. Availability is checked atomically on join." };
|
|
5007
5067
|
} else if (command === "create") {
|
|
5068
|
+
if (values.access && !["public", "invite", "private"].includes(values.access)) throw Error("Choose --access public, invite or private.");
|
|
5008
5069
|
const server = serviceUrl(values.server ?? "https://leafarcade-multiplayer.slawomir-radzyminski.workers.dev");
|
|
5009
5070
|
if (values.server && server !== "https://leafarcade-multiplayer.slawomir-radzyminski.workers.dev" && !values.site) throw Error("Provide --site for your custom server's browser frontend.");
|
|
5010
5071
|
const goSize = integer(values["go-size"] ?? "9", "go-size", 9, 19);
|
|
5011
5072
|
if (goSize !== 9 && goSize !== 13 && goSize !== 19) throw Error("go-size must be 9, 13 or 19.");
|
|
5012
|
-
result = await createRoom({ server, site: values.site ?? "https://leafarcade.com", game: values.game ?? "connect-four", name: values.name, capacity: values.capacity === void 0 ? void 0 : integer(values.capacity, "capacity", 2, 8), goSize, open: values.open }, controller.signal);
|
|
5073
|
+
result = await createRoom({ server, site: values.site ?? "https://leafarcade.com", game: values.game ?? "connect-four", name: values.name, capacity: values.capacity === void 0 ? void 0 : integer(values.capacity, "capacity", 2, 8), goSize, open: values.open, access: values.access, allowSpectators: !values["no-spectators"] }, controller.signal);
|
|
5013
5074
|
} else if (command === "rules") result = agentRules(values.game);
|
|
5014
5075
|
else {
|
|
5015
5076
|
const server = serviceUrl(values.server ?? "https://leafarcade-multiplayer.slawomir-radzyminski.workers.dev");
|
|
@@ -5022,7 +5083,8 @@ try {
|
|
|
5022
5083
|
if (name.length > 24 || /[\u0000-\u001f\u007f]/.test(name)) throw Error("Choose a display name of 1\u201324 characters without control characters.");
|
|
5023
5084
|
session ??= await createSession(path, server, room, name);
|
|
5024
5085
|
await rpc(session, "register", { name: session.name }, false, controller.signal);
|
|
5025
|
-
await rpc(session, "join", { agent: true }, false, controller.signal);
|
|
5086
|
+
const admission = await rpc(session, "join", { agent: true }, false, controller.signal);
|
|
5087
|
+
if (admission?.pending) output({ event: "access_requested", room, next: "Ask the host to approve your player request, then repeat join using this saved session." });
|
|
5026
5088
|
result = { event: "joined", position: agentPosition(await readRoom(session, controller.signal)) };
|
|
5027
5089
|
} else {
|
|
5028
5090
|
if (!session) throw Error("No local session for this room. Run leafarcade join ROOMCODE first.");
|
|
@@ -5033,7 +5095,11 @@ try {
|
|
|
5033
5095
|
result = await waitForRoom(session, integer(values["after-revision"], "after-revision", 0, Number.MAX_SAFE_INTEGER), integer(values.timeout ?? "25", "timeout", 1, 3600), controller.signal);
|
|
5034
5096
|
} else {
|
|
5035
5097
|
const view = await readRoom(session, controller.signal);
|
|
5036
|
-
if (command === "
|
|
5098
|
+
if (command === "approve" || command === "decline") {
|
|
5099
|
+
if (!values.request) throw Error("Provide --request ID from the host status access_requests.");
|
|
5100
|
+
await rpc(session, "approve", { revision: integer(values.revision, "revision", 0, Number.MAX_SAFE_INTEGER), id: values.request, accept: command === "approve" }, false, controller.signal);
|
|
5101
|
+
result = { event: command === "approve" ? "access_approved" : "access_declined", position: agentPosition(await readRoom(session, controller.signal)) };
|
|
5102
|
+
} else if (command === "open") {
|
|
5037
5103
|
if (values.server && server !== "https://leafarcade-multiplayer.slawomir-radzyminski.workers.dev" && !values.site) throw Error("Provide --site for your custom server\u2019s browser frontend.");
|
|
5038
5104
|
const url = roomUrl(values.site ?? "https://leafarcade.com", view.game, room);
|
|
5039
5105
|
result = { event: "browser_requested", room, url, browser: await openBrowser(url) };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leafarcade/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Create private browser board games and play with your AI agent. Includes
|
|
5
|
+
"description": "Create private browser board games and play with your AI agent. Includes 47 games and an installable Codex/Claude skill.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"leafarcade": "dist/cli.mjs"
|