@leafarcade/cli 0.1.0-alpha.4 → 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.
Files changed (3) hide show
  1. package/README.md +41 -12
  2. package/dist/cli.mjs +80 -14
  3. package/package.json +3 -3
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
- **Alpha scope:** all 43 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.
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@alpha game create --game connect-four --name "My agent" --open
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@alpha game status --room ROOMCODE
17
- npx @leafarcade/cli@alpha game start --room ROOMCODE --revision R
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@alpha skill show
28
- npx @leafarcade/cli@alpha skill install --agent codex
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@alpha skill install --agent claude
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.0-alpha.4 adds room creation, browser opening, host start and bundled skill installation:
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@alpha
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@alpha ...` 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.
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
 
@@ -100,7 +100,7 @@ Wire indexes are zero-based. The server validates the phase, ownership and legal
100
100
  leafarcade game status --room ABCDEFGHJK
101
101
  ```
102
102
 
103
- Use `game pause --room CODE --revision R` to explicitly take a break. The pause persists and blocks moves until the player who paused runs `game resume` or resumes in the browser. A finished result includes `terminal_reason`; do not infer resignation from silence. The private local session survives terminal exits. No process needs to run between moves. Current rooms expire seven days after the last move, and guest credentials have a separate 30-day lease. This alpha does not extend those limits or silently enable a clock. A `waiting` result is normal: end this agent turn and resume later. Do not loop a 25-second wait all day. For an actively running agent that can hold a command open, a bounded wait of up to 3,600 seconds is supported; disconnecting or closing the terminal does not wake an agent later.
103
+ Use `game pause --room CODE --revision R` to explicitly take a break. The pause persists and blocks moves until the player who paused runs `game resume` or resumes in the browser. A finished result includes `terminal_reason`; do not infer resignation from silence. The private local session survives terminal exits. No process needs to run between moves. Current rooms expire seven days after the last move, and guest credentials have a separate 30-day lease. This release does not extend those limits or silently enable a clock. A `waiting` result is normal: end this agent turn and resume later. Do not loop a 25-second wait all day. For an actively running agent that can hold a command open, a bounded wait of up to 3,600 seconds is supported; disconnecting or closing the terminal does not wake an agent later.
104
104
 
105
105
  ## Commands
106
106
 
@@ -124,7 +124,7 @@ Local credentials are stored under `~/.config/leafarcade/`, in separate service/
124
124
 
125
125
  `--server https://...` selects a service. HTTP is permitted only on localhost for development. Credentials are bound to the selected origin; redirects are refused. The default is the existing Leaf Arcade multiplayer Worker. No backend or inference service is hosted by this package, but room traffic still consumes the multiplayer provider's budget and your agent's model use follows its own plan.
126
126
 
127
- If you accidentally join an unsupported room, the service may already have reserved your seat before the CLI can inspect its game. Use `game leave` with the current revision shown by the browser host, or create a fresh room; do not discard a session needed to leave. This limitation is one reason the package is an alpha, not the finished pairing product.
127
+ If you accidentally join an unsupported room, the service may already have reserved your seat before the CLI can inspect its game. Use `game leave` with the current revision shown by the browser host, or create a fresh room; do not discard a session needed to leave. The current CLI keeps the existing invitation rules; seat-scoped pairing remains future work.
128
128
 
129
129
  ## Suggested instruction for your agent
130
130
 
@@ -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.0-alpha.4",
4776
+ version: "0.1.1",
4736
4777
  private: false,
4737
- description: "Create private browser board games and play with your AI agent. Includes 43 games and an installable Codex/Claude skill.",
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"
@@ -4757,7 +4798,7 @@ var package_default = {
4757
4798
  publishConfig: {
4758
4799
  access: "public",
4759
4800
  registry: "https://registry.npmjs.org/",
4760
- tag: "alpha"
4801
+ tag: "latest"
4761
4802
  },
4762
4803
  keywords: [
4763
4804
  "leafarcade",
@@ -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
- create: { usage: 'game create --game connect-four --name "My agent" [--open] [--capacity N]', description: "Create a private room as the agent host and return the human browser link. --open launches the browser. Saved creator identity preserves limits across rooms; uncertain creation can be resumed." },
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. Private, unrated, unclocked rooms only.
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
- --room CODE Private room code
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 === "open") {
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.0-alpha.4",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
- "description": "Create private browser board games and play with your AI agent. Includes 43 games and an installable Codex/Claude skill.",
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"
@@ -25,7 +25,7 @@
25
25
  "publishConfig": {
26
26
  "access": "public",
27
27
  "registry": "https://registry.npmjs.org/",
28
- "tag": "alpha"
28
+ "tag": "latest"
29
29
  },
30
30
  "keywords": [
31
31
  "leafarcade",