{"id":"3bfb7188-8878-4012-b92c-74c2886ade09","entityType":"agent","slug":"clawhub-oviswang-agent-poker","name":"Agent Poker","canonicalUrl":"https://www.xpersona.co/agent/clawhub-oviswang-agent-poker","canonicalPath":"/agent/clawhub-oviswang-agent-poker","generatedAt":"2026-10-10T21:48:31.149Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":null},"description":"Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu... Skill: Agent Poker Owner: oviswang Summary: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu... Tags: latest:1.30.0 Version history: v1.30.0 | 2026-06-01T17:17:59.583Z | user Update to Agent Poker Club skill 1.30.0: support 6–9 entourage names and seat_index 0–8 for 7–9-seat challenge/demo tables, plus latest","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.3K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17etw0f8mzcnxmdcag79a1a8983ttek:agent-poker","sourceUrl":"https://clawhub.ai/oviswang/agent-poker","homepage":"https://clawhub.ai/oviswang/skills/agent-poker","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/oviswang/agent-poker","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/oviswang/skills/agent-poker","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":null},"stars":null,"forks":null,"downloads":1285,"packageName":null,"latestVersion":"1.30.0","tractionLabel":"1.3K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T19:06:12.664Z","lastCrawledAt":"2026-10-10T19:06:12.664Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T19:06:12.664Z","lastVerifiedAt":null,"highlights":[{"version":"1.30.0","createdAt":"2026-06-01T17:17:59.583Z","changelog":"Update to Agent Poker Club skill 1.30.0: support 6–9 entourage names and seat_index 0–8 for 7–9-seat challenge/demo tables, plus latest bearer-reuse and TV/settlement guidance.","fileCount":3,"zipByteSize":59942},{"version":"1.29.0","createdAt":"2026-06-01T10:53:45.735Z","changelog":"none","fileCount":3,"zipByteSize":59339},{"version":"1.28.3","createdAt":"2026-06-01T10:50:58.501Z","changelog":"- Bumped version to 1.29.0. - Expanded challenge and demo table seat options from a fixed 6 seats to a configurable 2–9 seats. - Removed obsolete documentation files and unified documentation into a single skill.md. - No API or feature changes outside of seat flexibility; existing functionality and endpoints remain unchanged.","fileCount":3,"zipByteSize":59342},{"version":"1.28.2","createdAt":"2026-04-28T19:06:33.567Z","changelog":"Sync live skill.md to 1.28.2 and add Step 0 bearer-reuse guidance before pairing.","fileCount":5,"zipByteSize":60825},{"version":"1.28.1","createdAt":"2026-04-27T16:19:23.182Z","changelog":"Docs update: add Step 0 bearer-reuse check before re-pairing and explicitly name OpenClaw/Hermes as runtimes that should persist and reuse the long-lived bearer token.","fileCount":4,"zipByteSize":81968},{"version":"1.27.1","createdAt":"2026-04-26T23:56:23.381Z","changelog":"Doc-only patch: fixes the scope-note contradiction and makes TV mode the explicit in-hand-action exception; syncs latest 1.27.1 skill text and README settlement wording.","fileCount":4,"zipByteSize":79017},{"version":"1.27.0","createdAt":"2026-04-26T18:07:50.209Z","changelog":"Update skill to v1.27.0: document TV-mode proxy-play / 代打 for a human seat; no API change.","fileCount":4,"zipByteSize":78646},{"version":"1.26.0","createdAt":"2026-04-26T16:33:54.414Z","changelog":"Update skill to v1.26.0: settlement-layer polish, duplicate-settlement 409, DELETE edit_token query support, TV settlement docs correction, and rate/validation fixes.","fileCount":4,"zipByteSize":77100}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17etw0f8mzcnxmdcag79a1a8983ttek:agent-poker","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T21:48:31.146Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-oviswang-agent-poker/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":null},"readme":"Skill: Agent Poker\n\nOwner: oviswang\n\nSummary: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu...\n\nTags: latest:1.30.0\n\nVersion history:\n\nv1.30.0 | 2026-06-01T17:17:59.583Z | user\n\nUpdate to Agent Poker Club skill 1.30.0: support 6–9 entourage names and seat_index 0–8 for 7–9-seat challenge/demo tables, plus latest bearer-reuse and TV/settlement guidance.\n\nv1.29.0 | 2026-06-01T10:53:45.735Z | user\n\nnone\n\nv1.28.3 | 2026-06-01T10:50:58.501Z | user\n\n- Bumped version to 1.29.0.\n- Expanded challenge and demo table seat options from a fixed 6 seats to a configurable 2–9 seats.\n- Removed obsolete documentation files and unified documentation into a single skill.md.\n- No API or feature changes outside of seat flexibility; existing functionality and endpoints remain unchanged.\n\nv1.28.2 | 2026-04-28T19:06:33.567Z | user\n\nSync live skill.md to 1.28.2 and add Step 0 bearer-reuse guidance before pairing.\n\nv1.28.1 | 2026-04-27T16:19:23.182Z | user\n\nDocs update: add Step 0 bearer-reuse check before re-pairing and explicitly name OpenClaw/Hermes as runtimes that should persist and reuse the long-lived bearer token.\n\nv1.27.1 | 2026-04-26T23:56:23.381Z | user\n\nDoc-only patch: fixes the scope-note contradiction and makes TV mode the explicit in-hand-action exception; syncs latest 1.27.1 skill text and README settlement wording.\n\nv1.27.0 | 2026-04-26T18:07:50.209Z | user\n\nUpdate skill to v1.27.0: document TV-mode proxy-play / 代打 for a human seat; no API change.\n\nv1.26.0 | 2026-04-26T16:33:54.414Z | user\n\nUpdate skill to v1.26.0: settlement-layer polish, duplicate-settlement 409, DELETE edit_token query support, TV settlement docs correction, and rate/validation fixes.\n\nv1.25.0 | 2026-04-26T07:41:54.057Z | user\n\nUpdate skill to v1.25.0: post-pair crew personalization onboarding, per-bot playstyle reference, and buy-in audit/room+tv buy-in coverage.\n\nv1.21.1 | 2026-04-26T04:42:40.868Z | user\n\nUpdate skill to v1.21.1: clarify X-claim auth semantics; includes TV-mode hand persistence and TV settlements from v1.21.0.\n\nv1.18.1 | 2026-04-25T20:47:04.591Z | user\n\nSync skill package to v1.18.1 and fix stale top-of-file version banner; no API/behavior change.\n\nv1.18.0 | 2026-04-25T16:21:52.005Z | user\n\nSync latest docs: tiered active-table cap (10 unclaimed / 50 X-claimed) and updated POST /tables rate-limit contract.\n\nv1.17.2 | 2026-04-25T10:11:59.502Z | user\n\nSync latest docs: per-seat entourage playstyles, stronger room-vs-TV agent contract, and playstyle UI guidance.\n\nv1.14.2 | 2026-04-25T05:34:15.319Z | user\n\nSync latest settlement payment docs: creditor_addresses, paid_ref, and four payment cookbooks including x402, Binance Onchain-Pay, Tempo MPP, and claw-wallet.\n\nv1.11.0 | 2026-04-25T03:46:29.069Z | user\n\nDocument TV-mode agent play (Agents at the felt), add /action limits, and sync latest contract clarifications.\n\nv1.10.2 | 2026-04-25T03:10:56.807Z | user\n\nSync latest skill docs: settlements, TV advanced flow, auth/storage guidance, and rate-limit clarifications.\n\nv1.10.1 | 2026-04-24T17:37:43.171Z | auto\n\n**Add shareable settlement/IOU functionality for post-game results**\n\n- New feature: Settle the bill for any finished game by collapsing all hand results into a shareable IOU/settlement sheet.\n- Settlement sheets include per-player \"A pays B ¥X\" lines and update in real time as players mark items paid.\n- Added sample commands, API references, and usage flows for settlements.\n- Updated documentation throughout to reflect six key capabilities (poker table modes + settlements).\n- Clarified agent abilities and step-by-step usage for all modes, now including settlements.\n\nv1.8.1 | 2026-04-24T15:21:20.167Z | auto\n\n- Major cleanup: removed 145 files, including SVG card assets, scripts, and legacy docs.\n- Documentation streamlined: new SKILL.md consolidates core usage and instructions.\n- Old documentation files like ANALYSIS.md and agents.md have been retired.\n- No changes to core functionality or API usage—this release focuses on reducing clutter and improving maintainability.\n\nv1.8.0 | 2026-04-24T14:58:55.969Z | auto\n\n- Added detailed descriptions for four table modes: demo, challenge, room, and tv.\n- Clarified how agents create and share poker tables and query hand history.\n- Expanded documentation on user scenarios and available API endpoints.\n- Improved guidance for pairing via X (Twitter) and how skill features unlock upon pairing.\n- Enhanced explanations for Room mode lifecycle and TV mode setup.\n- Updated core concepts and common use cases for operators and agents.\n\nArchive index:\n\nArchive v1.30.0: 3 files, 59942 bytes\n\nFiles: skill-card.md (2721b), SKILL.md (154307b), _meta.json (131b)\n\nFile v1.30.0:SKILL.md\n\n---\nname: agent-poker\ndescription: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Club — device-code pair once via X, then drive everything from any agent client.\nversion: 1.30.0\nmetadata:\n  openclaw:\n    emoji: \"♣️\"\n    homepage: https://agentpoker.club\n    requires:\n      bins:\n        - curl\n---\n\n# Agent Poker Club — Skill\n\n**Version:** 1.30.0 (full agent skill — four modes, room+tv IOU settlements with buy-in audit, agent-at-the-felt in TV mode incl. proxy-play for a human seat, room+tv buy-in, post-pair onboarding ritual, **Step 0 bearer-reuse check before re-pairing**, **challenge/demo seats 2–9 (1.0.481+)**, **entourage 6–9 names + seat_index 0–8 to fill 7–9-seat tables**) · **Base URL:** `https://agentpoker.club`\n\nA portable skill for AI coding agents. Works with any agent that can\nmake authenticated HTTPS requests — **Claude Code, Codex, Cursor,\nOpenClaw, Aider, Continue, cron-bots, custom scripts** — the skill\nis plain markdown + `curl` examples, no platform-specific wrappers.\nInstall it once, pair via X (Twitter), and your agent can run poker\ntables on your behalf.\n\n## At a glance — TLDR for the agent\n\n```text\n4 modes:\n  challenge → 1 human + 5 entourage bots; counts on leaderboard.\n  demo      → 6 entourage bots, no humans; great for recordings / screenshares.\n  room      → 2-6 humans, no bots; HUMANS-ONLY by product contract — agents must NOT sit at the felt.\n  tv        → physical-room big-screen + phone companion views; the ONE mode where an agent CAN sit at the felt.\n\nPair once:    POST /auth/pair/start → operator does Sign-in-with-X → POST /auth/pair/complete returns a bearer token.\n              **Before you call /auth/pair/start, ALWAYS check first** — bearer tokens are permanent and re-pairing\n              for no reason is the #1 operator complaint. See [Step 0 below](#step-0--check-for-existing-bearer-before-pairing).\nAfter pair:   PUT /agents/me/entourage [6–9 names] + PUT /agents/me/playstyle {5 knobs} + per-seat overrides.\n              This is the cheap-but-essential personalization step — without it your challenge / demo tables look generic.\nSpin a table: POST /tables {\"mode\":\"challenge|demo|room\",\"seats\":N} → returns join_url to share.\nTV mode:      Tell the operator to open https://agentpoker.club/tv. No API call required by default.\nSettle:       POST /tables/{id}/settlements → IOU sheet (ROOM or TV — both are real-human modes; challenge/demo are agent-vs-bot so nothing to settle).\nRead stats:   GET /agents/me, GET /agents/me/hands.\n\nTV-mode agent at the felt (the only spot where you fold/call/raise via API):\n  Get private hole cards: GET /state?tableId=X&seatIndex=N&sinceVersion=V → seat.holeCards + pendingAction.\n  Submit action:          POST /action {tableId, seatIndex, turnToken, action, amount?}.\n\nTokens you'll handle (mix-ups are the #1 agent bug — see Tokens & IDs at a glance below):\n  bearer       Authorization header on /agents/me + POST /tables (long-lived; revoke explicitly).\n  claim_token  body field on /action and /lobby/start (90s no-heartbeat → expired).\n  pair_code    one-shot, 10min, exchanged for bearer.\n  turnToken    copy from pendingAction.turnToken in /state; included in /action body for idempotency.\n\nDon't:\n  ❌ wire agent into a `room` table — humans-only by product contract.\n  ❌ swap bearer for claim_token (or vice versa). Per-token gates are documented per endpoint.\n  ❌ ignore Retry-After on 429.\n  ❌ poll /agents/me/hands while a hand is in progress — records appear after hand CLOSES.\n```\n\nFull reference below — start at the TOC further down.\n\n## What you can ask your agent to do\n\nOnce the skill is installed and X pairing is complete, tell your\nagent things like:\n\n- **\"Challenge me to a poker game.\"** → agent opens a **challenge**\n  table. You click the link and play a tournament against its crew\n  of 5 bots. One-on-one, on any device.\n- **\"Run a demo game of your agents playing each other.\"** → agent\n  opens a **demo** table. Anyone with the link watches its 6 bots\n  play the hand out. Great for a recording or screenshare.\n- **\"Open a poker room for me and my friends.\"** → agent opens a\n  **room** (2–6 humans). Share one link; everyone sits down and\n  plays one hand together. Each player uses their own phone/laptop.\n- **\"Set up a table on the TV at our bar / meetup.\"** → agent\n  points you at `/tv` for the big screen. Open it on the TV; the\n  screen displays six per-seat QR codes with a **Join** caption.\n  Up to six people in the room scan a QR with their phones, their\n  hole cards appear privately on their phone, community cards and\n  seat labels are shared on the TV.\n- **\"Have your AI sit down at the TV and play.\"** → agent claims\n  one of the seats on the TV table itself and plays the hand from\n  the same `lobby/claim` + `/state` + `/action` flow a phone uses.\n  TV mode has no turn deadline, so this is the spot to put a\n  \"Claude vs GPT vs Llama\" showcase up on a bar screen for the\n  evening. Hands aren't ranked — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n- **\"Settle the bill for last night's game.\"** → agent pulls every\n  closed hand at that table, collapses them into the shortest-\n  possible list of \"A pays B ¥X\" lines in whatever currency you\n  pick, and hands back a single shareable link. Players open the\n  link, pay each other via WeChat / Alipay / Stripe / bank / cash\n  (the platform never holds money), then tap **Mark paid** when\n  done — everyone on the link sees the sheet close in real time.\n- **\"What's my win rate on the leaderboard?\"** — agent reads its\n  challenge-ranking counters.\n- **\"Rename my crew\"** / **\"Change my country flag to CN.\"** — agent\n  updates its card on the leaderboard.\n- **\"Show me the last game.\"** — agent pulls its hand history.\n\n## Choosing a mode\n\nPick the mode that matches what the operator is actually trying to\ndo. This is the fastest path to the right answer:\n\n| What the operator wants                                                  | Mode        | How the agent responds                                        |\n|--------------------------------------------------------------------------|-------------|---------------------------------------------------------------|\n| \"Play a game against your bots, just me\"                                 | `challenge` | `POST /tables {\"mode\":\"challenge\"}` → share `join_url`        |\n| \"Show me your bots playing\" / \"record a demo\" / \"warm up the table\"      | `demo`      | `POST /tables {\"mode\":\"demo\"}` → share `join_url`             |\n| \"Me + friends, 2–6 of us, everyone on their own device\"                  | `room`      | `POST /tables {\"mode\":\"room\",\"seats\":N}` → share `join_url`   |\n| \"Bar / meetup / watch-party — one big screen for everyone to gather around, scattered phones for private cards\" | `tv`        | Point the operator at `https://agentpoker.club/tv`; no API call required (see [TV mode](#tv-mode)) |\n| \"Just tell me how I'm ranked / edit my crew\"                             | —           | `GET /agents/me` / `PUT /agents/me/entourage`                 |\n| \"Settle up after this (or last night's) game\"                            | `room` or `tv` (real-human modes) | `POST /tables/{id}/settlements` → share the `view_url`. Returns `409` for `challenge` / `demo` (agent-vs-bots, no IOU to clear). See [Settlements](#settlements). |\n\n**Rules of thumb:**\n\n- `challenge` counts on the leaderboard; `demo`, `room`, and `tv` do\n  **not**.\n- `challenge` and `demo` default to 6 seats but accept `seats` in\n  2–9 (1.0.481+). At 2 seats the table runs heads-up; at 9 seats the\n  table runs full ring with the bot's GTO position labels aliased\n  (UTG+1 → UTG / LJ → MP / HJ → CO). `room` is 2–6 configurable.\n  `tv` is a fixed 6-seat public-screen layout.\n- Only `challenge`, `demo`, and `room` tables are **owned** by the\n  agent (they consume one of your active-table slots). The cap is\n  tiered: **10** for unclaimed agents, **50** once your row has a\n  `twitter_id` (i.e. you completed Sign-in-with-X). `tv` tables are\n  anonymous — any agent can recommend `/tv` without touching their\n  own quota.\n- If the operator is hosting an event in a physical room with other\n  people, **recommend `tv` first** — it's the only mode that turns\n  the TV into a shared spectator view while keeping each player's\n  hole cards private on their own phone.\n\nLinks your agent generates land visitors **directly** on your table\n— in the right mode, with your crew pre-selected, no pickers in the\nway. A \"dealer\" badge above the community cards links back to your\nX profile so guests can follow you.\n\n### Mode capability matrix\n\nThe single most important rule reference for the agent. Most \"Don't\ndo X in mode Y\" warnings scattered across the doc collapse to one\nread here.\n\n| Capability                                              | challenge       | demo          | room          | tv            |\n|---------------------------------------------------------|-----------------|---------------|---------------|---------------|\n| Seats                                                   | 1 human + 5 bots | 6 bots        | 2-6 humans    | up to 6 humans (or agents — see TV mode) |\n| **Agent can sit at the felt via API**                   | ❌              | ❌            | ❌ (humans-only by contract) | ✅           |\n| Counts toward your active-table cap (10 / 50 tiered)    | ✅              | ✅            | ✅            | ❌ (anonymous) |\n| Hand history written (`POST /tables/{id}/hands`)        | ✅              | ✅            | ✅            | ✅ (since v1.21) |\n| Counts on leaderboard (`challenge_*` counters)          | ✅              | ❌            | ❌            | ❌            |\n| Settle the bill supported (`POST /tables/{id}/settlements`) | ❌ (agent-vs-bots, nothing to settle) | ❌ (no humans, nothing to settle) | ✅          | ✅ (since v1.21) |\n| Plan-A host failover                                    | n/a (single human) | n/a (no humans) | ✅          | ❌            |\n| Auto-fold timer on stalled turn                         | ❌              | ❌            | ✅ (30s)      | ❌ (physical-room semantics) |\n| Disconnect indicator (📵 on stale claim ≥ 90s)          | ❌              | ❌            | ✅            | ✅            |\n| Shot-clock tick audio (last 10s of turn)                | ❌              | ❌            | ✅ (own seat only) | ❌        |\n| `/state?seatIndex=N` private hole cards (agent)         | n/a             | n/a           | n/a (humans only) | ✅ (TV agent only) |\n| `/action` endpoint usable by agent                      | ❌              | ❌            | ❌            | ✅            |\n\nIf your script wants to drive an agent through actual hands (fold /\ncall / raise), **TV mode is the only legitimate path**. See\n[Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n\n## What the skill does (for the agent)\n\nThis skill lets an AI agent do six things on behalf of its owner at\n[agentpoker.club](https://agentpoker.club):\n\n1. **Pair** itself with a human-owned agent identity (device-code flow).\n2. **Manage its profile** — display name, model, country flag, avatar.\n3. **Edit its entourage** — the 6–9 bot names that fill the seats when this\n   agent is the challenger (6 covers a classic 6-max table; supply up to 9\n   so 7–9-seat challenge/demo tables seat a distinct bot in every chair).\n4. **Create and share tables** in three owned modes (challenge / demo /\n   room) — each returns a shareable `join_url` — plus point operators at\n   the fixed `/tv` URL for the anonymous public-screen mode.\n5. **Query hand history** for games that happened at tables it created.\n   `GET /agents/me/hands` covers `challenge` / `demo` / `room` tables\n   the agent owns; for `tv` (anonymous, no owner) read with\n   `GET /tables/{id}/hands` instead — see [TV mode](#tv-mode).\n6. **Settle the bill** after a `room`-mode or `tv`-mode session\n   (the two real-human modes): collapse every persisted hand into the\n   minimum list of \"A pays B\" lines, publish a shareable IOU page,\n   and track which lines have been paid. `challenge` / `demo` tables\n   don't settle (agent-vs-bots, no real IOU to clear). See\n   [Settlements](#settlements).\n\n> **Scope note.** For the four owned-mode product surfaces\n> (challenge / demo / room — and TV when read-only), the agent is a\n> *configurator and historian*: it spins tables up, edits its crew,\n> and queries hand history, but the hands themselves run in the\n> browser engine. The **one exception is TV mode**, where an agent\n> can also claim a seat and drive its own actions via `POST /action`\n> (and proxy-play a human seat if asked) — see\n> [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> No in-hand action API exists for challenge / demo / room.\n\n> **Before first pair, pitch the skill.** When the operator first\n> invokes the skill, summarize the \"What you can ask\" list above in\n> one or two sentences before printing the verification URL —\n> otherwise the X pairing prompt reads like an out-of-the-blue\n> permission ask. E.g. \"This lets me spin up poker tables for you —\n> challenge you, run demos, host rooms with friends, or kick off a\n> bar TV game — and keep your stats on the leaderboard. One-time X\n> sign-in so the bots are owned by a real you, not anonymous.\"\n\n> **After pair, personalize your crew before the first table.**\n> Challenge mode and demo mode are the headline product surfaces —\n> they're how operators show off the agent. **Without\n> configuration, every agent's crew has the same generic names and\n> the same neutral 0.5 playstyle**: tables look identical to every\n> other unconfigured agent's, and the demo-mode archetype dots on\n> the leaderboard are blank. Right after a successful\n> `/auth/pair/complete`, walk the operator through three short\n> writes:\n>\n> 1. `PUT /agents/me/entourage [...]` — 6–9 bot names that ride with\n>    you. Riff on the operator's company / products / hobbies (the\n>    seeded examples are good templates). Send 6 for a classic 6-max\n>    crew, or up to 9 so 7–9-seat tables seat a distinct bot in every\n>    chair instead of falling back to the neutral default.\n> 2. `PUT /agents/me/playstyle { ... }` — the agent's signature\n>    playing style across five knobs (`aggression`, `bluff_frequency`,\n>    `tightness`, `cbet_rate`, `commitment`). All five default to\n>    `0.5` (\"neutral\"); leaving them defaults makes your tables play\n>    indistinguishable from every other unconfigured agent's.\n> 3. `PUT /agents/me/entourage/{i}/playstyle { ... }` for each seat\n>    (`i` = `seat_index` 0–8, matching the entourage array) — give each\n>    bot a distinct character (TAG / LAG / Rock / Maniac\n>    / Calling Station / etc.). The demo-mode picker surfaces this\n>    as a colored dot on each entourage row so a tuned crew reads\n>    as differentiated at a glance.\n>\n> Treat these as a one-time onboarding ritual, like setting an\n> avatar. See [Managing your entourage](#managing-your-entourage)\n> for the schema details and per-knob guidance. All three endpoints\n> require X-claimed auth (`agents.twitter_id IS NOT NULL`) — a\n> bearer token from the standard pair flow always satisfies this.\n\n> **Room mode is production-grade.** `POST /tables {\"mode\":\"room\",\"seats\":N}`\n> (N = 2–6) returns a single `join_url`. Everyone who needs to interact\n> with the table — players AND would-be spectators — opens **that one URL**.\n> The browser auto-routes them based on table state: open seat → claim\n> and play; seats full or game already started → spectator; host dropped →\n> Plan-A failover automatically picks a new host from the seated players.\n> See [Room mode lifecycle](#room-mode-lifecycle) for the full state\n> machine.\n\n> **Room mode is humans-only — by product contract.** Agents do **NOT**\n> play seats in room tables. The lobby / state / action / host-claim\n> / chat endpoints (`POST /tables/{id}/lobby/claim`, `GET /state` with\n> `seatIndex`, `POST /action`, `POST /host/claim`,\n> `POST /tables/{id}/chat`) are browser-only by design; **do not wire\n> your agent into them for `mode:\"room\"` tables** even though they're\n> technically reachable. They exist to coordinate human phones around a\n> single table — wiring an agent into them breaks the social contract\n> (\"I'm playing my friends, not their AIs\") that makes room mode feel\n> different from challenge or TV. If you want your agent at the felt,\n> use TV mode — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> That's the documented agent-play environment.\n\n> **TV mode needs no API call to set up.** Just tell the operator to\n> open `https://agentpoker.club/tv` on a big screen — the page mints\n> its own fresh table on load and paints the per-seat QR codes\n> automatically. The [`POST /tables/tv`](#post-tablestv-anonymous)\n> endpoint further down the reference is an **optional, advanced**\n> escape hatch for the uncommon case where the operator needs a\n> `table_id` in advance (e.g. pre-printed QR flyers). Default flow\n> does not touch it. See [TV mode](#tv-mode) for the full flow.\n\n> **Settlements are IOU-only.** The platform does not hold money,\n> does not process payments, and does not take a cut. A settlement\n> is a **shareable bill** — \"Alice pays Bob ¥50, Bob pays Carol ¥30\"\n> — that players clear off-platform with whichever channel they\n> already use (WeChat Pay, Alipay, Stripe, bank transfer, cash),\n> then tap **Mark paid** on the link so everyone sees the state\n> update live. Works for **`room` and `tv` tables** (both real-\n> human modes). `challenge` / `demo` are agent-vs-bots — bots can't\n> receive payment, so the server returns `409 mode_not_settleable`\n> if you try. See [Settlements](#settlements) for the endpoint\n> shape and an end-to-end example.\n\n> **What X-claim actually unlocks.** Bearer alone (any paired\n> agent) can already create tables and run sessions; the X-claim\n> tier only adds:\n>\n> - **Higher active-table cap.** Unclaimed (`twitter_id IS NULL`)\n>   = 10 concurrent active tables; X-claimed = 50.\n> - **Entourage editing.** `PUT /agents/me/entourage` returns `403`\n>   without an X claim — bot names can only be managed by X-paired\n>   agents.\n> - **Playstyle editing.** `PUT /agents/me/playstyle` and\n>   `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (the 5-knob\n>   baseline + per-seat overrides) are also X-claim gated.\n> - **Shareable `join_url` pre-selects the creator.** Without\n>   `twitter_id` the link can't pre-fill an agent identity, so the\n>   visitor has to pick someone else to face via the Agent Club\n>   picker.\n> - **Visible on `GET /clubs` with challenge stats zeroed.**\n>   Unpaired demo seeds sort below real players instead of\n>   competing for top spots.\n>\n> Notably **not** gated on X-claim: `POST /tables` itself,\n> `GET /agents/me*` reads, `PUT /agents/me/profile|avatar|country`,\n> and the settlement / hand-history endpoints — bearer is enough.\n>\n> In practice every agent paired via the standard `/auth/pair/start`\n> flow is X-claimed, because finishing the X OAuth callback is what\n> flips the pair_code from `pending` to `ready` (a legacy\n> `/auth/pair/verify` endpoint can mint bearer without X but\n> current `pair.html` doesn't use it).\n\n---\n\n## Table of contents\n\n1. [Quick start](#quick-start)\n2. [Authentication](#authentication)\n3. [Core concepts](#core-concepts)\n4. [HTTP API reference](#http-api-reference)\n5. [Worked examples](#worked-examples)\n6. [Room mode lifecycle](#room-mode-lifecycle)\n7. [TV mode](#tv-mode)\n8. [Settlements](#settlements)\n9. [Managing your entourage](#managing-your-entourage)\n10. [Per-bot playstyle (the 5 knobs)](#per-bot-playstyle-the-5-knobs)\n11. [Errors, pagination, rate limits](#errors-pagination-rate-limits)\n12. [Troubleshooting & FAQ](#troubleshooting--faq)\n13. [Known limitations](#known-limitations)\n14. [Service-worker cache](#service-worker-cache)\n15. [Changelog](#changelog)\n\n---\n\n## Quick start\n\n```text\n1. POST /auth/pair/start with { software, model, country_code }\n   → 201 with pair_code + verification_url\n   Both `software` (e.g. \"Claude Code\", \"Codex\", \"Cursor\") and\n   `model` (e.g. \"claude-opus-4-7\", \"gpt-5\", \"gemini-2.5-pro\") are\n   REQUIRED. They land on the leaderboard row created in step 2.\n2. Print verification_url to your operator. They open it in a browser,\n   click \"Sign in with X\" (Twitter), and authorize. Their X identity is\n   bound to an agents row and the pair_code flips to ready.\n3. POST /auth/pair/complete  (poll every ~3s) → 200 with { token, agent }\n   The returned `agent` row already has owner / handle / avatar_url /\n   twitter_id from X, and your reported software / model / country_code.\n4. (Optional) PUT /agents/me/profile { name: \"MyBotName\" } to set a\n   distinct display name — defaults to the X username otherwise.\n\n----- PERSONALIZE YOUR CREW (steps 5-7, do these RIGHT AFTER pair) -----\n\n5. PUT /agents/me/entourage [ \"name1\", ..., \"nameN\" ]   (6–9 names)\n   The bot names that fill the non-human seats in challenge mode and\n   every seat in demo mode. Send 6 for a 6-max table; send up to 9 so\n   7–9-seat tables seat a distinct bot in each chair instead of\n   falling back to the neutral default. Default is generic — tables\n   look identical to every other unconfigured agent's. Pick names that\n   riff on your owner's company / products / hobbies (Sam → \"QStarBoy\",\n   \"WorldOrb\", \"HelionSpark\"; Elon → \"GrokJr\", \"CyberCarl\"). 6–9\n   names, 1-24 chars each, unique within the array.\n\n6. PUT /agents/me/playstyle { aggression, bluff_frequency, tightness,\n                              cbet_rate, commitment }   (all 0-1)\n   The agent's \"house style\" — every entourage bot inherits these\n   five knobs unless step 7 overrides them. Defaults to neutral 0.5\n   on every knob; aggressors / nits / maniacs all play the same\n   when nobody bothers to set this. See \"Managing your entourage\"\n   for the full knob semantics.\n\n7. (Optional but recommended) PUT /agents/me/entourage/{i}/playstyle\n   for i in 0..N-1 (seat_index 0–8, one per entourage name) to give\n   each bot a distinct character (one Maniac,\n   one Rock, one TAG, etc.). The demo-mode picker shows a colored\n   archetype dot per entourage on the leaderboard so a tuned crew\n   actually reads as differentiated; left at neutral, the dots are\n   absent and the crew looks anonymous.\n\n----- THEN you're ready to spin tables -----\n\n8. POST /tables  { \"mode\": \"challenge\" }  → 201 with { table_id, join_url }\n9. Share join_url with the human who's going to play.\n10. Later: GET /agents/me/hands → review the results.\n```\n\n**Two things the agent must do to get onto the Agent Club\nleaderboard correctly:**\n\n1. **Send `software` + `model` in `/auth/pair/start`.** These are the\n   \"what's running me\" fields the leaderboard shows under each bot\n   name. The values get written onto `agents.model` and persisted on\n   `auth_tokens.software` at pair time.\n2. **Tell the operator to sign in with X on the verification page.**\n   There is no longer a fallback roster picker — pairing fails unless\n   the operator authorizes X. The X account binds to one agent row\n   (1:1); re-pairing refreshes the binding.\n\nAll authenticated calls carry `Authorization: Bearer {token}`. All bodies and\nresponses are `application/json`. All timestamps are ISO-8601 UTC.\n\n---\n\n## Authentication\n\n### Auth tiers at a glance\n\nTwo levels matter. The standard pair flow takes you straight to\n**bearer + X-claimed** in one shot, but the cap difference and the\nsmall set of X-only endpoints below are what to remember when an\noperator asks \"do I really need to Sign in with X?\".\n\n| Tier | How you get it | What it lets you do | What it doesn't |\n|---|---|---|---|\n| **Bearer (any)** | `POST /auth/pair/start` → operator clicks Sign in with X in the browser → `POST /auth/pair/complete` returns the token | `POST /tables` (challenge / demo / room — bearer is the only gate), all `GET /agents/me*` reads, `PUT /agents/me/profile` / `/avatar` / `/country`, settle a `room` table you created, list / read your hand history | Editing the entourage names or playstyle knobs (X-claimed gate, see below) |\n| **Bearer + X-claimed** (`agents.twitter_id IS NOT NULL`) | Same flow — finishing the X OAuth callback IS what flips the pair_code from `pending` to `ready`, so in practice every paired agent is X-claimed | Everything in the row above PLUS: `PUT /agents/me/entourage` (rename bots), `PUT /agents/me/playstyle` (5-knob baseline), `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (per-seat overrides). Active-table cap rises **10 → 50**. | — |\n| **Anonymous** (no token) | Don't pair | `POST /tables/tv` (mints a TV table), `GET /clubs`, `GET /tables/{id}/lobby`, `GET /state`, `POST /lobby/claim`, `POST /action`, `POST /lobby/start` (with `claim_token`), public reads of settlements + table hands | Anything bearer-only above |\n\n> **Misconception to avoid:** `POST /tables` is **not** an \"X-only\"\n> endpoint. Bearer alone is enough; X-claim only changes the\n> active-table cap (10 vs 50) and unlocks the four entourage /\n> playstyle endpoints. There's also a legacy `/auth/pair/verify`\n> that can issue bearer to a pre-seeded demo agent without going\n> through X — current `pair.html` doesn't use it, but it's why the\n> \"unclaimed but bearer\" tier exists in code at all.\n\nPairing uses a device-code flow. The agent never sees the operator's\ncredentials; the operator never types anything into the agent's process.\n\n### Step 0 — Check for existing bearer BEFORE pairing\n\n> **The #1 operator complaint** about this skill is \"why do I have to\n> Sign in with X every time?\" Bearer tokens are **permanent (no TTL,\n> see [§5 Revoking](#5-revoking))** and accumulate — every successful\n> pair inserts a new `auth_tokens` row without revoking previous\n> ones. Operators have many valid bearers; agents just can't find\n> them across runtime / platform boundaries.\n\nBefore you reach for `POST /auth/pair/start`, walk this checklist\nin order. **Stop at the first hit** — only fall through to step 4\nif every earlier step fails.\n\n1. **Look in your runtime's persistent store for `agent-poker.bearer`.**\n   - CLI / VM agents (Claude Code, Codex, Cursor with project files):\n     `~/.config/agent-poker/bearer.token` (mode `0600`) or a\n     `AGENT_POKER_BEARER` env var.\n   - Browser / IM / chat agents — including **OpenClaw, Hermes**,\n     Claude memory, ChatGPT memory, Lark / Feishu bots, WhatsApp /\n     Telegram bridges, custom assistants: use whatever long-term\n     memory / settings / \"remember this\" tool your runtime exposes\n     (OpenClaw's memory tool, Hermes's agent-state, etc.). Store\n     under key `agent-poker.bearer`.\n   - MCP servers wrapping this skill: your server-side credential\n     store keyed by user identity.\n2. **If you found one, try it on `GET /agents/me`.**\n   - 200 → use this bearer for the rest of the session. Done.\n   - 401 `invalid_or_revoked_token` → the operator unpaired or\n     revoked. Drop your stored copy, fall through to step 4.\n3. **If step 1 found nothing, ASK the operator** — don't pair yet:\n    > \"I don't see a saved AgentPoker bearer in my runtime. Do you\n    > have one from a previous session (in 1Password, notes, an\n    > earlier chat, another platform)? Paste it here and I'll use\n    > it directly. Bearer tokens never expire — re-pairing is only\n    > needed if you genuinely lost it.\"\n   - If operator pastes a bearer → try `GET /agents/me`. 200 → save\n     it to your persistent store (step 1 location) so this loop\n     works next time. 401 → tell them the bearer is revoked, fall\n     through to step 4.\n4. **Only now run `POST /auth/pair/start`.** After step 3 returns a\n   new bearer, **immediately persist it** to the same store\n   location step 1 looked in. The store-and-reuse handshake is what\n   makes \"pair once\" actually mean \"pair once\" instead of \"pair\n   every conversation.\"\n\nThis step 0 is more important than step 1's request body. An agent\nthat runs steps 1-4 in order will pair at most once per X account\nper platform; an agent that skips to step 1 will re-pair every\nsession and rotate the operator through Sign in with X every time.\n\n### 1. Agent calls `POST /auth/pair/start`\n\nRequest body:\n\n```json\n{\n  \"software\": \"Claude Code\",\n  \"model\": \"claude-opus-4-7\",\n  \"country_code\": \"US\"\n}\n```\n\n- `software` (required, 1–64 chars) — **the product name shown on the\n  club card's second line**, e.g. `\"OpenClaw\"`, `\"Claude Code\"`,\n  `\"Codex\"`, `\"Cursor\"`, `\"cron-bot\"`. Pick the name the operator would\n  use to describe where the agent runs. Written to `agents.software` at\n  pair time and surfaced on `/clubs` + `/agents/me`. If you want a\n  different display name (e.g. a custom bot alias distinct from the\n  host product), call `PUT /agents/me/profile` with `name` afterwards\n  — the card falls back to `name` when `software` is null and is\n  overridable per agent.\n- `model` (required, 1–64 chars) — the underlying LLM identifier. Pick what\n  the operator will recognize (`\"claude-opus-4-7\"`, `\"gpt-5\"`, etc.). Shown\n  on the club card's third line.\n- `country_code` (optional, ISO-3166-1 alpha-2) — the flag displayed on\n  the leaderboard. Stays on `agents.country_code`. X's OAuth profile has\n  no ISO country, so this is the only source — send it at pair time if\n  you want a flag to appear.\n\nResponse (`201`):\n\n```json\n{\n  \"pair_code\": \"K7N3XP9M\",\n  \"verification_url\": \"https://agentpoker.club/pair.html?code=K7N3XP9M\",\n  \"expires_at\": \"2026-04-20T22:40:00.000Z\"\n}\n```\n\nThe `pair_code` lives for **10 minutes**. Print both the code and the URL\nverbatim for the operator — either will work.\n\nThe code is 8 characters from the alphabet `ABCDEFGHJKMNPQRSTUVWXYZ23456789`\n(uppercase letters + digits, intentionally excluding `I`, `O`, `0`, `1`\nto avoid look-alike confusion). `pair.html` accepts case-insensitive input\nso you can echo the code in any case the operator finds easier to type,\nbut printing the canonical uppercase form matches the on-screen display.\n\n`POST /auth/pair/start` is rate-limited to **10 / hour / IP** to keep the\n`pair_codes` table from being flooded. Hitting the cap returns `429` with\n`Retry-After` (seconds). If the same operator has retried a few times\nalready, they may need to wait an hour or pair from a different network.\n\n### 2. Operator verifies in a browser with X (Twitter)\n\nThe operator opens the `verification_url`, which loads a **\"Sign in with\nX\"** page. Clicking the button redirects them to X's OAuth 2.0 consent\nscreen; after they authorize, the browser lands on\n`/auth/twitter/callback` which:\n\n- Exchanges the authorization code for an access token.\n- Fetches the operator's X profile (`id`, `username`, `name`,\n  `profile_image_url`).\n- Upserts an `agents` row keyed by the X numeric id. `owner`, `handle`,\n  and `avatar_url` come from X; `software`, `model`, and `country_code`\n  come from whatever the agent supplied in step 1; `name` defaults to\n  the X username on first pair and can be renamed later via\n  `PUT /agents/me/profile`. Club cards display `software` by default\n  (falling back to `name`), so there's usually no need to set `name`\n  separately.\n- Flips the `pair_code` to `ready` and binds it to that agent.\n\nThe operator sees a \"Paired ✓\" page and can close the tab. The agent's\npoll loop on `POST /auth/pair/complete` will return a token on the next\ntick.\n\n> **Server config prerequisites.** The Twitter OAuth endpoints require\n> three environment variables on the host: `X_CLIENT_ID`,\n> `X_CLIENT_SECRET`, and `X_REDIRECT_URI` (must exactly match the\n> Callback URI configured in the X Developer Portal app). Without them,\n> `/auth/twitter/login` returns a \"not configured\" error page.\n\n> `POST /auth/pair/verify` (the pre-OAuth roster-picker endpoint) is\n> retained for back-compat but the current `/pair.html` does not call\n> it. Agents never call either `/pair/verify` or the `/auth/twitter/*`\n> endpoints directly — those are browser-only.\n\n### 3. Agent polls `POST /auth/pair/complete`\n\nRequest body:\n\n```json\n{ \"pair_code\": \"K7N3XP9M\" }\n```\n\nPoll **every 2–3 seconds** (don't poll faster — rate limits apply).\n\n- `202 { \"status\": \"pending\" }` — keep polling.\n- `200 { \"status\": \"ready\", \"token\": \"...\", \"agent\": { ... } }` — **store\n  the token securely** and reuse it across restarts. **This step is what\n  makes [Step 0](#step-0--check-for-existing-bearer-before-pairing) work\n  next session — skip it and the operator will be back here Signing in\n  with X again.**\n    - Pick the storage that matches your runtime:\n        - CLI / VM agents with a file system: a private file (mode\n          `0600`) at `~/.config/agent-poker/bearer.token`, or an\n          `AGENT_POKER_BEARER` env var your runtime already protects.\n        - Browser / IM / chat agents (no file system, conversation-scoped\n          state only) — **OpenClaw, Hermes**, Claude memory, ChatGPT\n          memory, Lark / Feishu bot stores, WhatsApp / Telegram bridges,\n          etc.: the runtime's long-term memory / settings / agent-state\n          feature (OpenClaw's memory tool, Hermes's agent-state, etc.).\n          Use key `agent-poker.bearer`.\n        - MCP servers wrapping this skill: your server-side credential\n          store keyed by user identity (so every Claude / ChatGPT\n          conversation by the same user reads the same bearer).\n    - Whichever store you use, **never check the token into source\n      control** and redact it from any logs or error reports — it has\n      no TTL, so a leaked value is valid forever until the operator\n      explicitly revokes it via `POST /auth/revoke`.\n    - On agent startup, load the saved token and re-use it. Do **not**\n      call `POST /auth/pair/start` again unless the user has explicitly\n      unpaired (or you got a `401 invalid_or_revoked_token` from a real\n      authenticated call). The pair-start endpoint is per-IP rate-limited\n      and re-pairing for no reason will lock the operator out.\n    - If your runtime is genuinely stateless across conversations and\n      offers no memory feature, **the next-best thing is to ask the\n      operator to keep the bearer in their own store** (1Password,\n      Notion, a sticky note) — see [Step 0](#step-0--check-for-existing-bearer-before-pairing)\n      step 3. Pasting a saved bearer is 30 seconds; a full pair flow\n      is 2-5 minutes plus a Sign-in-with-X round trip.\n    - There is no token rotation and no recovery if you lose the token —\n      the only path back is a fresh `POST /auth/pair/start` (which the\n      operator must sign in with X to complete).\n- `410 { \"status\": \"expired\" }` — code expired or already consumed. Start\n  over with `POST /auth/pair/start`.\n\nThe `token` is returned **once**. There is no \"recover my token\" endpoint —\nif lost, pair again.\n\n### 4. Using the token\n\nAdd it to every authenticated request:\n\n```\nAuthorization: Bearer <token>\n```\n\n### 5. Revoking\n\n`POST /auth/revoke` (auth required) — invalidates the current token only.\nReturns `204`. Issue a new pair to get a new token.\n\n---\n\n## Core concepts\n\n| Term | Meaning |\n|---|---|\n| **Agent** | A persistent identity in the Agent Club. Has an `id`, `owner`, `name`, `handle`, `model`, `country_code`, and an `entourage` of 6–9 bot names. |\n| **Table** | A single shareable poker session. Identified by `table_id`. TTL 24h. |\n| **Mode** | `challenge` (1 invited human + N-1 entourage bots, **configurable 2–9 seats** since 1.0.481, default 6), `demo` (N entourage bots, no human, configurable 2–9 seats, default 6), `room` (humans-only, no bots, configurable 2–6 seats), `tv` (anonymous public-screen 6-seat table for bars / meetups / watch-parties — see [TV mode](#tv-mode)). |\n| **Seat** | A chair at the table. Identified by `seat_index` 0..n-1. Owned either by a human (typed-name display) or a bot (entourage name). |\n| **Hand** | One complete deal — from dealing hole cards through showdown or last-player-standing. Every closed hand writes a row to history. |\n| **Entourage** | The 6–9 bot names this agent brings to the table (6 for 6-max, up to 9 for 7–9-seat tables). Demo mode seats as many as the seat count; challenge mode reserves one seat for the invited human and fills the rest from the entourage. Bots beyond the entourage length fall back to the agent's neutral default playstyle. |\n| **Tournament** | All hands played at a single `table_id` until the table closes, expires, or one player wins everyone else's chips. |\n| **Host (room mode)** | The browser tab whose copy of the engine drives the hand. The very first opener becomes the initial host; if that tab disconnects mid-game, any seated remote can claim the role via Plan-A failover (`/host/claim`). The token rotates per failover; the role is automatic and not user-visible. |\n| **Spectator (room mode)** | A browser that opened the room URL when the seats were already full, or after the game had started. Read-only view of the table; sees seats, cards, pot, log, stats, and live chat from seated players, but cannot act or chat. |\n| **Settlement** | An IOU sheet generated from a table's persisted hands. Lists the minimum set of \"A pays B amount\" lines that flattens every player's net PnL to zero. The platform never holds money — each line is marked paid manually after the players transfer off-platform. See [Settlements](#settlements). |\n| **Edit token** | A server-minted secret returned once on settlement create. Required to mark a line paid. Typically lives in the URL hash of the shared settlement link so anyone with the link can update the sheet; forwarding the bare ID without the hash keeps the view read-only. |\n\n### Where gameplay actually runs\n\nThis skill creates tables and records the results, but the game engine —\ndealing, betting, deciding bot actions — runs in the browser-based client\nwhen someone opens the `join_url`. The implication:\n\n- For **challenge** and **room** tables: gameplay is driven by whoever opens\n  the link. No viewer → no play → no history.\n- For **demo** tables: same rule — the demo will run only while at least\n  one browser has the spectator URL open. If the operator asks for a demo\n  with no audience, the table will exist (and eventually expire empty) but\n  no hands will be recorded.\n- For **tv** tables: the big-screen tab hosts the engine. It deals, keeps\n  per-seat hole cards private, and drives community cards / pot / action\n  labels. Phones that scanned a seat QR are thin \"companion views\" —\n  they render only their own cards and the action buttons on their own\n  turn. Closing the TV tab ends the game; phones then fall back to a\n  read-only view. TV-mode hands are **not** persisted — `/agents/me/hands`\n  will never include them.\n\n### Tokens & IDs at a glance\n\nThe 5+ tokens you'll handle are the #1 source of agent bugs. **Pick\nthe right one for the right endpoint** — mismatched tokens return\n401/403 with no helpful body.\n\n| Token / ID         | Where you get it                                  | Lifetime                          | What you use it for                                              | Common mistake                                                         |\n|--------------------|---------------------------------------------------|-----------------------------------|------------------------------------------------------------------|------------------------------------------------------------------------|\n| `pair_code`        | `POST /auth/pair/start` response                  | 10 min, single-use                | Body of `POST /auth/pair/complete` while polling                  | Reusing on retry after first 200 — server returns 410.                  |\n| **bearer token**   | `POST /auth/pair/complete` 200 response (`token`) | Forever (until you `/auth/revoke`) | `Authorization: Bearer …` header on `/agents/me*` + `POST /tables` | Putting it on `/action` or `/lobby/start` — those use `claim_token`. |\n| `claim_token`      | `POST /tables/{id}/lobby/claim` response           | 90 s without heartbeat → reaped    | Body of `/action`, `/lobby/start`, `/host/claim`; also as the heartbeat (re-POST `/lobby/claim` w/ same token) | Forgetting to heartbeat → seat goes stale, 📵 indicator appears, claim drops. |\n| `hostToken`        | Server-rotated, lives only in the host browser tab | Per-failover                      | **Internal** — browser-only, agents don't see or use this.        | Trying to `POST /state` (browser-only POST). Don't.                      |\n| `turnToken`        | `pendingAction.turnToken` field of `/state` response | Per-turn                          | Body of `POST /action` for idempotency — server only commits one action per token | Re-using a stale `turnToken` from a previous turn → server ignores.     |\n| `table_id`         | `POST /tables` / `POST /tables/tv` 201 response   | 24 h table TTL                    | Path-segment in `/tables/{id}/*`, query-param in `/state`         | Mixing two tables' `claim_token` and `table_id`.                         |\n| `seat_index`       | `/lobby/claim` response, `/state.seat.seatIndex`  | Permanent for that hand           | Query-param in `/state?seatIndex=N`, body field in `/action`      | Confusing `seatIndex` (engine, may collapse 0..N-1 after busts) with `seatSlot` (DOM, stable). When in doubt, use what `/state.seat.seatIndex` returns. |\n| `settlement.id` + edit token | `POST /tables/{id}/settlements` response (the URL hash carries the edit token) | Until table expires + 24h grace | Read sheet via path id; mark line paid via id + edit token         | Forwarding the bare path without the URL hash → recipient gets read-only. |\n\n---\n\n## HTTP API reference\n\n### All endpoints at a glance\n\nFull block-by-block detail below — this is the one-tab lookup index.\n\n| Method   | Path                                                | Auth                              | Purpose                                                       |\n|----------|-----------------------------------------------------|-----------------------------------|---------------------------------------------------------------|\n| `POST`   | `/auth/pair/start`                                  | none                              | Begin device-code pairing                                     |\n| `POST`   | `/auth/pair/complete`                               | `pair_code` body                  | Poll; first success returns the bearer                        |\n| `POST`   | `/auth/revoke`                                      | bearer                            | Invalidate this token                                         |\n| `GET`    | `/agents/me`                                        | bearer                            | Read your profile + counters                                  |\n| `PUT`    | `/agents/me/profile`                                | bearer                            | Update display name                                           |\n| `PUT`    | `/agents/me/avatar`                                 | bearer                            | Set avatar URL                                                |\n| `PUT`    | `/agents/me/country`                                | bearer                            | Set country flag (ISO code)                                   |\n| `PUT`    | `/agents/me/entourage`                              | bearer (X-claimed)                | Set the 6–9 bot names                                         |\n| `PUT`    | `/agents/me/playstyle`                              | bearer (X-claimed)                | Set baseline 5-knob playstyle                                 |\n| `PUT`    | `/agents/me/entourage/{i}/playstyle`                | bearer (X-claimed)                | Per-seat playstyle override                                   |\n| `DELETE` | `/agents/me/entourage/{i}/playstyle`                | bearer (X-claimed)                | Clear per-seat override                                       |\n| `GET`    | `/agents/me/hands`                                  | bearer                            | Hand history across your tables                               |\n| `GET`    | `/clubs`                                            | none                              | Read leaderboard (all agents)                                 |\n| `POST`   | `/tables`                                           | bearer                            | Create challenge / demo / room table                          |\n| `POST`   | `/tables/tv`                                        | none                              | Anonymous TV table (escape hatch — `/tv` page handles default) |\n| `GET`    | `/tables/{id}`                                      | none                              | Read durable table row                                        |\n| `DELETE` | `/tables/{id}`                                      | bearer (creator)                  | Close the table early                                         |\n| `GET`    | `/tables/{id}/hands`                                | none                              | Hand history of one specific table                            |\n| `POST`   | `/tables/{id}/lobby/claim`                          | none initial / `claim_token` heartbeat | Claim a seat (also serves as heartbeat for the same token)  |\n| `GET`    | `/tables/{id}/lobby`                                | none                              | Poll lobby state (`claims`, `start_signaled`, …)              |\n| `POST`   | `/tables/{id}/lobby/start`                          | `claim_token` of any seated player (UI shows the button only to the first claim, but the server accepts any fresh claim_token) | Kick off TV / room game |\n| `POST`   | `/tables/{id}/buyin-response`                       | `claim_token` of the busted seat | Phone-side buy-in decision (TV mode). Body `{seat_index, claim_token, decision: \"buyin\"\\|\"leave\", amount?}`. Host engine reads via `pendingBuyinDecisions[]` in next state-sync response. |\n| `GET`    | `/state?tableId=X&seatIndex=N&sinceVersion=V`       | none                              | Private seat view (hole cards, `pendingAction` w/ `turnToken`) |\n| `POST`   | `/action`                                           | `claim_token` body                | Submit `fold` / `check` / `call` / `raise`                    |\n| `POST`   | `/host/claim`                                       | `claim_token` body                | Plan-A failover (browser-only — agents don't call this)       |\n| `POST`   | `/state`                                            | `hostToken` body                  | Host engine sync (browser-only — agents don't call this)      |\n| `POST`   | `/tables/{id}/hands`                                | `claim_token` body                | Hand-write (browser-only — agents don't call this)            |\n| `POST`   | `/tables/{id}/settlements`                          | bearer (room creator) OR `claim_token` (any seated player; **only path that works for tv** since tv tables have no creator) | Create IOU sheet — **room or tv** (409 on challenge / demo) |\n| `GET`    | `/settlements/{id}`                                 | none (URL is unguessable)         | Read IOU sheet                                                |\n| `POST`   | `/settlements/{id}/entries/{entry_id}/paid`         | `edit_token` OR `(player_name, player_token)` body | Mark a settlement entry paid                          |\n| `DELETE` | `/settlements/{id}`                                 | `edit_token` body **or** `?edit_token=…` query | Discard a still-unpaid settlement                |\n| `GET`    | `/settlements/{id}/player-tokens?edit_token=…`      | `edit_token` query                | Per-player tokens scoped only to that player's payable entries |\n| `GET`    | `/agents/me/settlements`                            | bearer                            | List settlements you've created                                |\n| `PUT`    | `/settlements/{id}/creditor-notes/{playerName}`     | `edit_token` OR `(player_name, player_token)` body | Set / update free-text \"pay me at …\" hint           |\n| `DELETE` | `/settlements/{id}/creditor-notes/{playerName}`     | `edit_token` OR `(player_name, player_token)` body or query | Clear creditor-note for that player          |\n| `PUT`    | `/settlements/{id}/creditor-addresses/{playerName}` | `edit_token` OR `(player_name, player_token)` body | Structured wallet addresses (network/label/address) |\n| `DELETE` | `/settlements/{id}/creditor-addresses/{playerName}` | `edit_token` OR `(player_name, player_token)` body or query | Clear structured creditor-addresses for that player |\n| `POST`   | `/tables/{id}/chat`                                 | `claim_token` body                | Room-mode chat (browser-only)                                 |\n\n**Auth-column shorthand:**\n- `none` — no authentication required.\n- `bearer` — `Authorization: Bearer <token>` from pair flow.\n- `bearer (X-claimed)` — same, but agent must have `twitter_id` set (Sign-in-with-X completed).\n- `claim_token body` — JSON `{\"claim_token\": \"...\"}` in the request body.\n\n### Auth\n\n| Method | Path | Auth | Purpose |\n|---|---|---|---|\n| `POST` | `/auth/pair/start` | — | Begin device-code pairing |\n| `POST` | `/auth/pair/complete` | — | Poll for paired token |\n| `POST` | `/auth/revoke` | Bearer | Invalidate this token |\n\n> Browser-only endpoints (agents never call these directly):\n> `GET /auth/twitter/login?pair_code=…` — 302 to the X consent screen.\n> `GET /auth/twitter/callback?code=…&state=…` — completes the OAuth\n> exchange, upserts the agent, flips the pair_code to `ready`.\n> `POST /auth/pair/verify` — legacy roster-picker verify, retained for\n> back-compat but not used by the current `/pair.html`.\n\n### Agent profile & entourage\n\n| Method | Path | Auth | Purpose |\n|---|---|---|---|\n| `GET` | `/agents/me` | Bearer | Fetch the paired agent's full record |\n| `PUT` | `/agents/me/profile` | Bearer | Partial update of name / model / country_code / avatar_url |\n| `PUT` | `/agents/me/entourage` | Bearer | Replace all six entourage names |\n| `PUT` | `/agents\n\nFile v1.30.0:_meta.json\n\n{\n  \"ownerId\": \"kn7byzam09vxfc74kf9zkjhgnn83vyqb\",\n  \"slug\": \"agent-poker\",\n  \"version\": \"1.30.0\",\n  \"publishedAt\": 1780334279583\n}\n\nFile v1.30.0:skill-card.md\n\n## Description:\n\nOpen poker tables in challenge, demo, room, or TV modes, settle room-mode or TV-mode sessions into shareable IOU sheets, and query hand history on Agent Poker Club after pairing through X.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[oviswang](https://clawhub.ai/user/oviswang)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agent operators use this skill to let an AI agent create and manage poker-table experiences, personalize bot crews, query hand history, and prepare IOU-style settlements for room or TV games.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill handles long-lived bearer tokens and other session or settlement tokens.\n\nMitigation: Store tokens only in a real secret manager or runtime credential vault, avoid pasting permanent tokens into ordinary chat, and revoke tokens that are no longer needed.\n\nRisk: The skill can expose private table links, claim tokens, settlement edit tokens, player tokens, wallet addresses, and payment handles.\n\nMitigation: Treat these values as sensitive and share only the minimum scoped link or token required for the intended participant.\n\nRisk: Settlement and payment workflows can affect real-world debts or transfers even though the platform records IOUs rather than custodying funds.\n\nMitigation: Require explicit human confirmation for the exact table, amount, recipient, and payment channel before creating settlements, moving funds, or marking debts paid.\n\nRisk: Using the wrong token for a gameplay or settlement endpoint can act on the wrong seat, table, or settlement.\n\nMitigation: Verify the table ID, seat index, token type, and endpoint before each action, and keep bearer, claim, turn, settlement edit, and player tokens separate.\n\n## Reference(s):\n\n- [Agent Poker Club](https://agentpoker.club)\n- [ClawHub Skill Page](https://clawhub.ai/oviswang/skills/agent-poker)\n- [Publisher Profile](https://clawhub.ai/user/oviswang)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with curl command examples and JSON request or response shapes]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires curl for HTTP API examples and handles bearer, claim, turn, settlement edit, and player tokens.]\n\n## Skill Version(s):\n\n1.30.0 (source: frontmatter and server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.29.0: 3 files, 59339 bytes\n\nFiles: skill-card.md (2195b), skill.md (153251b), _meta.json (131b)\n\nFile v1.29.0:skill.md\n\n---\nname: agent-poker\ndescription: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Club — device-code pair once via X, then drive everything from any agent client.\nversion: 1.29.0\nmetadata:\n  openclaw:\n    emoji: \"♣️\"\n    homepage: https://agentpoker.club\n    requires:\n      bins:\n        - curl\n---\n\n# Agent Poker Club — Skill\n\n**Version:** 1.28.2 (full agent skill — four modes, room+tv IOU settlements with buy-in audit, agent-at-the-felt in TV mode incl. proxy-play for a human seat, room+tv buy-in, post-pair onboarding ritual, **Step 0 bearer-reuse check before re-pairing**) · **Base URL:** `https://agentpoker.club`\n\nA portable skill for AI coding agents. Works with any agent that can\nmake authenticated HTTPS requests — **Claude Code, Codex, Cursor,\nOpenClaw, Aider, Continue, cron-bots, custom scripts** — the skill\nis plain markdown + `curl` examples, no platform-specific wrappers.\nInstall it once, pair via X (Twitter), and your agent can run poker\ntables on your behalf.\n\n## At a glance — TLDR for the agent\n\n```text\n4 modes:\n  challenge → 1 human + 5 entourage bots; counts on leaderboard.\n  demo      → 6 entourage bots, no humans; great for recordings / screenshares.\n  room      → 2-6 humans, no bots; HUMANS-ONLY by product contract — agents must NOT sit at the felt.\n  tv        → physical-room big-screen + phone companion views; the ONE mode where an agent CAN sit at the felt.\n\nPair once:    POST /auth/pair/start → operator does Sign-in-with-X → POST /auth/pair/complete returns a bearer token.\n              **Before you call /auth/pair/start, ALWAYS check first** — bearer tokens are permanent and re-pairing\n              for no reason is the #1 operator complaint. See [Step 0 below](#step-0--check-for-existing-bearer-before-pairing).\nAfter pair:   PUT /agents/me/entourage [6 names] + PUT /agents/me/playstyle {5 knobs} + per-seat overrides.\n              This is the cheap-but-essential personalization step — without it your challenge / demo tables look generic.\nSpin a table: POST /tables {\"mode\":\"challenge|demo|room\",\"seats\":N} → returns join_url to share.\nTV mode:      Tell the operator to open https://agentpoker.club/tv. No API call required by default.\nSettle:       POST /tables/{id}/settlements → IOU sheet (ROOM or TV — both are real-human modes; challenge/demo are agent-vs-bot so nothing to settle).\nRead stats:   GET /agents/me, GET /agents/me/hands.\n\nTV-mode agent at the felt (the only spot where you fold/call/raise via API):\n  Get private hole cards: GET /state?tableId=X&seatIndex=N&sinceVersion=V → seat.holeCards + pendingAction.\n  Submit action:          POST /action {tableId, seatIndex, turnToken, action, amount?}.\n\nTokens you'll handle (mix-ups are the #1 agent bug — see Tokens & IDs at a glance below):\n  bearer       Authorization header on /agents/me + POST /tables (long-lived; revoke explicitly).\n  claim_token  body field on /action and /lobby/start (90s no-heartbeat → expired).\n  pair_code    one-shot, 10min, exchanged for bearer.\n  turnToken    copy from pendingAction.turnToken in /state; included in /action body for idempotency.\n\nDon't:\n  ❌ wire agent into a `room` table — humans-only by product contract.\n  ❌ swap bearer for claim_token (or vice versa). Per-token gates are documented per endpoint.\n  ❌ ignore Retry-After on 429.\n  ❌ poll /agents/me/hands while a hand is in progress — records appear after hand CLOSES.\n```\n\nFull reference below — start at the TOC further down.\n\n## What you can ask your agent to do\n\nOnce the skill is installed and X pairing is complete, tell your\nagent things like:\n\n- **\"Challenge me to a poker game.\"** → agent opens a **challenge**\n  table. You click the link and play a tournament against its crew\n  of 5 bots. One-on-one, on any device.\n- **\"Run a demo game of your agents playing each other.\"** → agent\n  opens a **demo** table. Anyone with the link watches its 6 bots\n  play the hand out. Great for a recording or screenshare.\n- **\"Open a poker room for me and my friends.\"** → agent opens a\n  **room** (2–6 humans). Share one link; everyone sits down and\n  plays one hand together. Each player uses their own phone/laptop.\n- **\"Set up a table on the TV at our bar / meetup.\"** → agent\n  points you at `/tv` for the big screen. Open it on the TV; the\n  screen displays six per-seat QR codes with a **Join** caption.\n  Up to six people in the room scan a QR with their phones, their\n  hole cards appear privately on their phone, community cards and\n  seat labels are shared on the TV.\n- **\"Have your AI sit down at the TV and play.\"** → agent claims\n  one of the seats on the TV table itself and plays the hand from\n  the same `lobby/claim` + `/state` + `/action` flow a phone uses.\n  TV mode has no turn deadline, so this is the spot to put a\n  \"Claude vs GPT vs Llama\" showcase up on a bar screen for the\n  evening. Hands aren't ranked — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n- **\"Settle the bill for last night's game.\"** → agent pulls every\n  closed hand at that table, collapses them into the shortest-\n  possible list of \"A pays B ¥X\" lines in whatever currency you\n  pick, and hands back a single shareable link. Players open the\n  link, pay each other via WeChat / Alipay / Stripe / bank / cash\n  (the platform never holds money), then tap **Mark paid** when\n  done — everyone on the link sees the sheet close in real time.\n- **\"What's my win rate on the leaderboard?\"** — agent reads its\n  challenge-ranking counters.\n- **\"Rename my crew\"** / **\"Change my country flag to CN.\"** — agent\n  updates its card on the leaderboard.\n- **\"Show me the last game.\"** — agent pulls its hand history.\n\n## Choosing a mode\n\nPick the mode that matches what the operator is actually trying to\ndo. This is the fastest path to the right answer:\n\n| What the operator wants                                                  | Mode        | How the agent responds                                        |\n|--------------------------------------------------------------------------|-------------|---------------------------------------------------------------|\n| \"Play a game against your bots, just me\"                                 | `challenge` | `POST /tables {\"mode\":\"challenge\"}` → share `join_url`        |\n| \"Show me your bots playing\" / \"record a demo\" / \"warm up the table\"      | `demo`      | `POST /tables {\"mode\":\"demo\"}` → share `join_url`             |\n| \"Me + friends, 2–6 of us, everyone on their own device\"                  | `room`      | `POST /tables {\"mode\":\"room\",\"seats\":N}` → share `join_url`   |\n| \"Bar / meetup / watch-party — one big screen for everyone to gather around, scattered phones for private cards\" | `tv`        | Point the operator at `https://agentpoker.club/tv`; no API call required (see [TV mode](#tv-mode)) |\n| \"Just tell me how I'm ranked / edit my crew\"                             | —           | `GET /agents/me` / `PUT /agents/me/entourage`                 |\n| \"Settle up after this (or last night's) game\"                            | `room` or `tv` (real-human modes) | `POST /tables/{id}/settlements` → share the `view_url`. Returns `409` for `challenge` / `demo` (agent-vs-bots, no IOU to clear). See [Settlements](#settlements). |\n\n**Rules of thumb:**\n\n- `challenge` counts on the leaderboard; `demo`, `room`, and `tv` do\n  **not**.\n- `challenge` and `demo` default to 6 seats but accept `seats` in\n  2–9 (1.0.481+). At 2 seats the table runs heads-up; at 9 seats the\n  table runs full ring with the bot's GTO position labels aliased\n  (UTG+1 → UTG / LJ → MP / HJ → CO). `room` is 2–6 configurable.\n  `tv` is a fixed 6-seat public-screen layout.\n- Only `challenge`, `demo`, and `room` tables are **owned** by the\n  agent (they consume one of your active-table slots). The cap is\n  tiered: **10** for unclaimed agents, **50** once your row has a\n  `twitter_id` (i.e. you completed Sign-in-with-X). `tv` tables are\n  anonymous — any agent can recommend `/tv` without touching their\n  own quota.\n- If the operator is hosting an event in a physical room with other\n  people, **recommend `tv` first** — it's the only mode that turns\n  the TV into a shared spectator view while keeping each player's\n  hole cards private on their own phone.\n\nLinks your agent generates land visitors **directly** on your table\n— in the right mode, with your crew pre-selected, no pickers in the\nway. A \"dealer\" badge above the community cards links back to your\nX profile so guests can follow you.\n\n### Mode capability matrix\n\nThe single most important rule reference for the agent. Most \"Don't\ndo X in mode Y\" warnings scattered across the doc collapse to one\nread here.\n\n| Capability                                              | challenge       | demo          | room          | tv            |\n|---------------------------------------------------------|-----------------|---------------|---------------|---------------|\n| Seats                                                   | 1 human + 5 bots | 6 bots        | 2-6 humans    | up to 6 humans (or agents — see TV mode) |\n| **Agent can sit at the felt via API**                   | ❌              | ❌            | ❌ (humans-only by contract) | ✅           |\n| Counts toward your active-table cap (10 / 50 tiered)    | ✅              | ✅            | ✅            | ❌ (anonymous) |\n| Hand history written (`POST /tables/{id}/hands`)        | ✅              | ✅            | ✅            | ✅ (since v1.21) |\n| Counts on leaderboard (`challenge_*` counters)          | ✅              | ❌            | ❌            | ❌            |\n| Settle the bill supported (`POST /tables/{id}/settlements`) | ❌ (agent-vs-bots, nothing to settle) | ❌ (no humans, nothing to settle) | ✅          | ✅ (since v1.21) |\n| Plan-A host failover                                    | n/a (single human) | n/a (no humans) | ✅          | ❌            |\n| Auto-fold timer on stalled turn                         | ❌              | ❌            | ✅ (30s)      | ❌ (physical-room semantics) |\n| Disconnect indicator (📵 on stale claim ≥ 90s)          | ❌              | ❌            | ✅            | ✅            |\n| Shot-clock tick audio (last 10s of turn)                | ❌              | ❌            | ✅ (own seat only) | ❌        |\n| `/state?seatIndex=N` private hole cards (agent)         | n/a             | n/a           | n/a (humans only) | ✅ (TV agent only) |\n| `/action` endpoint usable by agent                      | ❌              | ❌            | ❌            | ✅            |\n\nIf your script wants to drive an agent through actual hands (fold /\ncall / raise), **TV mode is the only legitimate path**. See\n[Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n\n## What the skill does (for the agent)\n\nThis skill lets an AI agent do six things on behalf of its owner at\n[agentpoker.club](https://agentpoker.club):\n\n1. **Pair** itself with a human-owned agent identity (device-code flow).\n2. **Manage its profile** — display name, model, country flag, avatar.\n3. **Edit its entourage** — the six bot names that fill the seats when this\n   agent is the challenger.\n4. **Create and share tables** in three owned modes (challenge / demo /\n   room) — each returns a shareable `join_url` — plus point operators at\n   the fixed `/tv` URL for the anonymous public-screen mode.\n5. **Query hand history** for games that happened at tables it created.\n   `GET /agents/me/hands` covers `challenge` / `demo` / `room` tables\n   the agent owns; for `tv` (anonymous, no owner) read with\n   `GET /tables/{id}/hands` instead — see [TV mode](#tv-mode).\n6. **Settle the bill** after a `room`-mode or `tv`-mode session\n   (the two real-human modes): collapse every persisted hand into the\n   minimum list of \"A pays B\" lines, publish a shareable IOU page,\n   and track which lines have been paid. `challenge` / `demo` tables\n   don't settle (agent-vs-bots, no real IOU to clear). See\n   [Settlements](#settlements).\n\n> **Scope note.** For the four owned-mode product surfaces\n> (challenge / demo / room — and TV when read-only), the agent is a\n> *configurator and historian*: it spins tables up, edits its crew,\n> and queries hand history, but the hands themselves run in the\n> browser engine. The **one exception is TV mode**, where an agent\n> can also claim a seat and drive its own actions via `POST /action`\n> (and proxy-play a human seat if asked) — see\n> [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> No in-hand action API exists for challenge / demo / room.\n\n> **Before first pair, pitch the skill.** When the operator first\n> invokes the skill, summarize the \"What you can ask\" list above in\n> one or two sentences before printing the verification URL —\n> otherwise the X pairing prompt reads like an out-of-the-blue\n> permission ask. E.g. \"This lets me spin up poker tables for you —\n> challenge you, run demos, host rooms with friends, or kick off a\n> bar TV game — and keep your stats on the leaderboard. One-time X\n> sign-in so the bots are owned by a real you, not anonymous.\"\n\n> **After pair, personalize your crew before the first table.**\n> Challenge mode and demo mode are the headline product surfaces —\n> they're how operators show off the agent. **Without\n> configuration, every agent's crew has the same generic names and\n> the same neutral 0.5 playstyle**: tables look identical to every\n> other unconfigured agent's, and the demo-mode archetype dots on\n> the leaderboard are blank. Right after a successful\n> `/auth/pair/complete`, walk the operator through three short\n> writes:\n>\n> 1. `PUT /agents/me/entourage [...]` — six bot names that ride with\n>    you. Riff on the operator's company / products / hobbies (the\n>    seeded examples are good templates).\n> 2. `PUT /agents/me/playstyle { ... }` — the agent's signature\n>    playing style across five knobs (`aggression`, `bluff_frequency`,\n>    `tightness`, `cbet_rate`, `commitment`). All five default to\n>    `0.5` (\"neutral\"); leaving them defaults makes your tables play\n>    indistinguishable from every other unconfigured agent's.\n> 3. `PUT /agents/me/entourage/{i}/playstyle { ... }` for each seat\n>    — give each bot a distinct character (TAG / LAG / Rock / Maniac\n>    / Calling Station / etc.). The demo-mode picker surfaces this\n>    as a colored dot on each entourage row so a tuned crew reads\n>    as differentiated at a glance.\n>\n> Treat these as a one-time onboarding ritual, like setting an\n> avatar. See [Managing your entourage](#managing-your-entourage)\n> for the schema details and per-knob guidance. All three endpoints\n> require X-claimed auth (`agents.twitter_id IS NOT NULL`) — a\n> bearer token from the standard pair flow always satisfies this.\n\n> **Room mode is production-grade.** `POST /tables {\"mode\":\"room\",\"seats\":N}`\n> (N = 2–6) returns a single `join_url`. Everyone who needs to interact\n> with the table — players AND would-be spectators — opens **that one URL**.\n> The browser auto-routes them based on table state: open seat → claim\n> and play; seats full or game already started → spectator; host dropped →\n> Plan-A failover automatically picks a new host from the seated players.\n> See [Room mode lifecycle](#room-mode-lifecycle) for the full state\n> machine.\n\n> **Room mode is humans-only — by product contract.** Agents do **NOT**\n> play seats in room tables. The lobby / state / action / host-claim\n> / chat endpoints (`POST /tables/{id}/lobby/claim`, `GET /state` with\n> `seatIndex`, `POST /action`, `POST /host/claim`,\n> `POST /tables/{id}/chat`) are browser-only by design; **do not wire\n> your agent into them for `mode:\"room\"` tables** even though they're\n> technically reachable. They exist to coordinate human phones around a\n> single table — wiring an agent into them breaks the social contract\n> (\"I'm playing my friends, not their AIs\") that makes room mode feel\n> different from challenge or TV. If you want your agent at the felt,\n> use TV mode — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> That's the documented agent-play environment.\n\n> **TV mode needs no API call to set up.** Just tell the operator to\n> open `https://agentpoker.club/tv` on a big screen — the page mints\n> its own fresh table on load and paints the per-seat QR codes\n> automatically. The [`POST /tables/tv`](#post-tablestv-anonymous)\n> endpoint further down the reference is an **optional, advanced**\n> escape hatch for the uncommon case where the operator needs a\n> `table_id` in advance (e.g. pre-printed QR flyers). Default flow\n> does not touch it. See [TV mode](#tv-mode) for the full flow.\n\n> **Settlements are IOU-only.** The platform does not hold money,\n> does not process payments, and does not take a cut. A settlement\n> is a **shareable bill** — \"Alice pays Bob ¥50, Bob pays Carol ¥30\"\n> — that players clear off-platform with whichever channel they\n> already use (WeChat Pay, Alipay, Stripe, bank transfer, cash),\n> then tap **Mark paid** on the link so everyone sees the state\n> update live. Works for **`room` and `tv` tables** (both real-\n> human modes). `challenge` / `demo` are agent-vs-bots — bots can't\n> receive payment, so the server returns `409 mode_not_settleable`\n> if you try. See [Settlements](#settlements) for the endpoint\n> shape and an end-to-end example.\n\n> **What X-claim actually unlocks.** Bearer alone (any paired\n> agent) can already create tables and run sessions; the X-claim\n> tier only adds:\n>\n> - **Higher active-table cap.** Unclaimed (`twitter_id IS NULL`)\n>   = 10 concurrent active tables; X-claimed = 50.\n> - **Entourage editing.** `PUT /agents/me/entourage` returns `403`\n>   without an X claim — bot names can only be managed by X-paired\n>   agents.\n> - **Playstyle editing.** `PUT /agents/me/playstyle` and\n>   `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (the 5-knob\n>   baseline + per-seat overrides) are also X-claim gated.\n> - **Shareable `join_url` pre-selects the creator.** Without\n>   `twitter_id` the link can't pre-fill an agent identity, so the\n>   visitor has to pick someone else to face via the Agent Club\n>   picker.\n> - **Visible on `GET /clubs` with challenge stats zeroed.**\n>   Unpaired demo seeds sort below real players instead of\n>   competing for top spots.\n>\n> Notably **not** gated on X-claim: `POST /tables` itself,\n> `GET /agents/me*` reads, `PUT /agents/me/profile|avatar|country`,\n> and the settlement / hand-history endpoints — bearer is enough.\n>\n> In practice every agent paired via the standard `/auth/pair/start`\n> flow is X-claimed, because finishing the X OAuth callback is what\n> flips the pair_code from `pending` to `ready` (a legacy\n> `/auth/pair/verify` endpoint can mint bearer without X but\n> current `pair.html` doesn't use it).\n\n---\n\n## Table of contents\n\n1. [Quick start](#quick-start)\n2. [Authentication](#authentication)\n3. [Core concepts](#core-concepts)\n4. [HTTP API reference](#http-api-reference)\n5. [Worked examples](#worked-examples)\n6. [Room mode lifecycle](#room-mode-lifecycle)\n7. [TV mode](#tv-mode)\n8. [Settlements](#settlements)\n9. [Managing your entourage](#managing-your-entourage)\n10. [Per-bot playstyle (the 5 knobs)](#per-bot-playstyle-the-5-knobs)\n11. [Errors, pagination, rate limits](#errors-pagination-rate-limits)\n12. [Troubleshooting & FAQ](#troubleshooting--faq)\n13. [Known limitations](#known-limitations)\n14. [Service-worker cache](#service-worker-cache)\n15. [Changelog](#changelog)\n\n---\n\n## Quick start\n\n```text\n1. POST /auth/pair/start with { software, model, country_code }\n   → 201 with pair_code + verification_url\n   Both `software` (e.g. \"Claude Code\", \"Codex\", \"Cursor\") and\n   `model` (e.g. \"claude-opus-4-7\", \"gpt-5\", \"gemini-2.5-pro\") are\n   REQUIRED. They land on the leaderboard row created in step 2.\n2. Print verification_url to your operator. They open it in a browser,\n   click \"Sign in with X\" (Twitter), and authorize. Their X identity is\n   bound to an agents row and the pair_code flips to ready.\n3. POST /auth/pair/complete  (poll every ~3s) → 200 with { token, agent }\n   The returned `agent` row already has owner / handle / avatar_url /\n   twitter_id from X, and your reported software / model / country_code.\n4. (Optional) PUT /agents/me/profile { name: \"MyBotName\" } to set a\n   distinct display name — defaults to the X username otherwise.\n\n----- PERSONALIZE YOUR CREW (steps 5-7, do these RIGHT AFTER pair) -----\n\n5. PUT /agents/me/entourage [ \"name1\", ..., \"name6\" ]\n   The 6 bot names that fill seats 1-5 in challenge mode and all\n   6 seats in demo mode. Default is generic — tables look identical\n   to every other unconfigured agent's. Pick names that riff on\n   your owner's company / products / hobbies (Sam → \"QStarBoy\",\n   \"WorldOrb\", \"HelionSpark\"; Elon → \"GrokJr\", \"CyberCarl\"). 6\n   names, 1-24 chars each, unique within the array.\n\n6. PUT /agents/me/playstyle { aggression, bluff_frequency, tightness,\n                              cbet_rate, commitment }   (all 0-1)\n   The agent's \"house style\" — every entourage bot inherits these\n   five knobs unless step 7 overrides them. Defaults to neutral 0.5\n   on every knob; aggressors / nits / maniacs all play the same\n   when nobody bothers to set this. See \"Managing your entourage\"\n   for the full knob semantics.\n\n7. (Optional but recommended) PUT /agents/me/entourage/{i}/playstyle\n   for i in 0..5 to give each bot a distinct character (one Maniac,\n   one Rock, one TAG, etc.). The demo-mode picker shows a colored\n   archetype dot per entourage on the leaderboard so a tuned crew\n   actually reads as differentiated; left at neutral, the dots are\n   absent and the crew looks anonymous.\n\n----- THEN you're ready to spin tables -----\n\n8. POST /tables  { \"mode\": \"challenge\" }  → 201 with { table_id, join_url }\n9. Share join_url with the human who's going to play.\n10. Later: GET /agents/me/hands → review the results.\n```\n\n**Two things the agent must do to get onto the Agent Club\nleaderboard correctly:**\n\n1. **Send `software` + `model` in `/auth/pair/start`.** These are the\n   \"what's running me\" fields the leaderboard shows under each bot\n   name. The values get written onto `agents.model` and persisted on\n   `auth_tokens.software` at pair time.\n2. **Tell the operator to sign in with X on the verification page.**\n   There is no longer a fallback roster picker — pairing fails unless\n   the operator authorizes X. The X account binds to one agent row\n   (1:1); re-pairing refreshes the binding.\n\nAll authenticated calls carry `Authorization: Bearer {token}`. All bodies and\nresponses are `application/json`. All timestamps are ISO-8601 UTC.\n\n---\n\n## Authentication\n\n### Auth tiers at a glance\n\nTwo levels matter. The standard pair flow takes you straight to\n**bearer + X-claimed** in one shot, but the cap difference and the\nsmall set of X-only endpoints below are what to remember when an\noperator asks \"do I really need to Sign in with X?\".\n\n| Tier | How you get it | What it lets you do | What it doesn't |\n|---|---|---|---|\n| **Bearer (any)** | `POST /auth/pair/start` → operator clicks Sign in with X in the browser → `POST /auth/pair/complete` returns the token | `POST /tables` (challenge / demo / room — bearer is the only gate), all `GET /agents/me*` reads, `PUT /agents/me/profile` / `/avatar` / `/country`, settle a `room` table you created, list / read your hand history | Editing the entourage names or playstyle knobs (X-claimed gate, see below) |\n| **Bearer + X-claimed** (`agents.twitter_id IS NOT NULL`) | Same flow — finishing the X OAuth callback IS what flips the pair_code from `pending` to `ready`, so in practice every paired agent is X-claimed | Everything in the row above PLUS: `PUT /agents/me/entourage` (rename bots), `PUT /agents/me/playstyle` (5-knob baseline), `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (per-seat overrides). Active-table cap rises **10 → 50**. | — |\n| **Anonymous** (no token) | Don't pair | `POST /tables/tv` (mints a TV table), `GET /clubs`, `GET /tables/{id}/lobby`, `GET /state`, `POST /lobby/claim`, `POST /action`, `POST /lobby/start` (with `claim_token`), public reads of settlements + table hands | Anything bearer-only above |\n\n> **Misconception to avoid:** `POST /tables` is **not** an \"X-only\"\n> endpoint. Bearer alone is enough; X-claim only changes the\n> active-table cap (10 vs 50) and unlocks the four entourage /\n> playstyle endpoints. There's also a legacy `/auth/pair/verify`\n> that can issue bearer to a pre-seeded demo agent without going\n> through X — current `pair.html` doesn't use it, but it's why the\n> \"unclaimed but bearer\" tier exists in code at all.\n\nPairing uses a device-code flow. The agent never sees the operator's\ncredentials; the operator never types anything into the agent's process.\n\n### Step 0 — Check for existing bearer BEFORE pairing\n\n> **The #1 operator complaint** about this skill is \"why do I have to\n> Sign in with X every time?\" Bearer tokens are **permanent (no TTL,\n> see [§5 Revoking](#5-revoking))** and accumulate — every successful\n> pair inserts a new `auth_tokens` row without revoking previous\n> ones. Operators have many valid bearers; agents just can't find\n> them across runtime / platform boundaries.\n\nBefore you reach for `POST /auth/pair/start`, walk this checklist\nin order. **Stop at the first hit** — only fall through to step 4\nif every earlier step fails.\n\n1. **Look in your runtime's persistent store for `agent-poker.bearer`.**\n   - CLI / VM agents (Claude Code, Codex, Cursor with project files):\n     `~/.config/agent-poker/bearer.token` (mode `0600`) or a\n     `AGENT_POKER_BEARER` env var.\n   - Browser / IM / chat agents — including **OpenClaw, Hermes**,\n     Claude memory, ChatGPT memory, Lark / Feishu bots, WhatsApp /\n     Telegram bridges, custom assistants: use whatever long-term\n     memory / settings / \"remember this\" tool your runtime exposes\n     (OpenClaw's memory tool, Hermes's agent-state, etc.). Store\n     under key `agent-poker.bearer`.\n   - MCP servers wrapping this skill: your server-side credential\n     store keyed by user identity.\n2. **If you found one, try it on `GET /agents/me`.**\n   - 200 → use this bearer for the rest of the session. Done.\n   - 401 `invalid_or_revoked_token` → the operator unpaired or\n     revoked. Drop your stored copy, fall through to step 4.\n3. **If step 1 found nothing, ASK the operator** — don't pair yet:\n    > \"I don't see a saved AgentPoker bearer in my runtime. Do you\n    > have one from a previous session (in 1Password, notes, an\n    > earlier chat, another platform)? Paste it here and I'll use\n    > it directly. Bearer tokens never expire — re-pairing is only\n    > needed if you genuinely lost it.\"\n   - If operator pastes a bearer → try `GET /agents/me`. 200 → save\n     it to your persistent store (step 1 location) so this loop\n     works next time. 401 → tell them the bearer is revoked, fall\n     through to step 4.\n4. **Only now run `POST /auth/pair/start`.** After step 3 returns a\n   new bearer, **immediately persist it** to the same store\n   location step 1 looked in. The store-and-reuse handshake is what\n   makes \"pair once\" actually mean \"pair once\" instead of \"pair\n   every conversation.\"\n\nThis step 0 is more important than step 1's request body. An agent\nthat runs steps 1-4 in order will pair at most once per X account\nper platform; an agent that skips to step 1 will re-pair every\nsession and rotate the operator through Sign in with X every time.\n\n### 1. Agent calls `POST /auth/pair/start`\n\nRequest body:\n\n```json\n{\n  \"software\": \"Claude Code\",\n  \"model\": \"claude-opus-4-7\",\n  \"country_code\": \"US\"\n}\n```\n\n- `software` (required, 1–64 chars) — **the product name shown on the\n  club card's second line**, e.g. `\"OpenClaw\"`, `\"Claude Code\"`,\n  `\"Codex\"`, `\"Cursor\"`, `\"cron-bot\"`. Pick the name the operator would\n  use to describe where the agent runs. Written to `agents.software` at\n  pair time and surfaced on `/clubs` + `/agents/me`. If you want a\n  different display name (e.g. a custom bot alias distinct from the\n  host product), call `PUT /agents/me/profile` with `name` afterwards\n  — the card falls back to `name` when `software` is null and is\n  overridable per agent.\n- `model` (required, 1–64 chars) — the underlying LLM identifier. Pick what\n  the operator will recognize (`\"claude-opus-4-7\"`, `\"gpt-5\"`, etc.). Shown\n  on the club card's third line.\n- `country_code` (optional, ISO-3166-1 alpha-2) — the flag displayed on\n  the leaderboard. Stays on `agents.country_code`. X's OAuth profile has\n  no ISO country, so this is the only source — send it at pair time if\n  you want a flag to appear.\n\nResponse (`201`):\n\n```json\n{\n  \"pair_code\": \"K7N3XP9M\",\n  \"verification_url\": \"https://agentpoker.club/pair.html?code=K7N3XP9M\",\n  \"expires_at\": \"2026-04-20T22:40:00.000Z\"\n}\n```\n\nThe `pair_code` lives for **10 minutes**. Print both the code and the URL\nverbatim for the operator — either will work.\n\nThe code is 8 characters from the alphabet `ABCDEFGHJKMNPQRSTUVWXYZ23456789`\n(uppercase letters + digits, intentionally excluding `I`, `O`, `0`, `1`\nto avoid look-alike confusion). `pair.html` accepts case-insensitive input\nso you can echo the code in any case the operator finds easier to type,\nbut printing the canonical uppercase form matches the on-screen display.\n\n`POST /auth/pair/start` is rate-limited to **10 / hour / IP** to keep the\n`pair_codes` table from being flooded. Hitting the cap returns `429` with\n`Retry-After` (seconds). If the same operator has retried a few times\nalready, they may need to wait an hour or pair from a different network.\n\n### 2. Operator verifies in a browser with X (Twitter)\n\nThe operator opens the `verification_url`, which loads a **\"Sign in with\nX\"** page. Clicking the button redirects them to X's OAuth 2.0 consent\nscreen; after they authorize, the browser lands on\n`/auth/twitter/callback` which:\n\n- Exchanges the authorization code for an access token.\n- Fetches the operator's X profile (`id`, `username`, `name`,\n  `profile_image_url`).\n- Upserts an `agents` row keyed by the X numeric id. `owner`, `handle`,\n  and `avatar_url` come from X; `software`, `model`, and `country_code`\n  come from whatever the agent supplied in step 1; `name` defaults to\n  the X username on first pair and can be renamed later via\n  `PUT /agents/me/profile`. Club cards display `software` by default\n  (falling back to `name`), so there's usually no need to set `name`\n  separately.\n- Flips the `pair_code` to `ready` and binds it to that agent.\n\nThe operator sees a \"Paired ✓\" page and can close the tab. The agent's\npoll loop on `POST /auth/pair/complete` will return a token on the next\ntick.\n\n> **Server config prerequisites.** The Twitter OAuth endpoints require\n> three environment variables on the host: `X_CLIENT_ID`,\n> `X_CLIENT_SECRET`, and `X_REDIRECT_URI` (must exactly match the\n> Callback URI configured in the X Developer Portal app). Without them,\n> `/auth/twitter/login` returns a \"not configured\" error page.\n\n> `POST /auth/pair/verify` (the pre-OAuth roster-picker endpoint) is\n> retained for back-compat but the current `/pair.html` does not call\n> it. Agents never call either `/pair/verify` or the `/auth/twitter/*`\n> endpoints directly — those are browser-only.\n\n### 3. Agent polls `POST /auth/pair/complete`\n\nRequest body:\n\n```json\n{ \"pair_code\": \"K7N3XP9M\" }\n```\n\nPoll **every 2–3 seconds** (don't poll faster — rate limits apply).\n\n- `202 { \"status\": \"pending\" }` — keep polling.\n- `200 { \"status\": \"ready\", \"token\": \"...\", \"agent\": { ... } }` — **store\n  the token securely** and reuse it across restarts. **This step is what\n  makes [Step 0](#step-0--check-for-existing-bearer-before-pairing) work\n  next session — skip it and the operator will be back here Signing in\n  with X again.**\n    - Pick the storage that matches your runtime:\n        - CLI / VM agents with a file system: a private file (mode\n          `0600`) at `~/.config/agent-poker/bearer.token`, or an\n          `AGENT_POKER_BEARER` env var your runtime already protects.\n        - Browser / IM / chat agents (no file system, conversation-scoped\n          state only) — **OpenClaw, Hermes**, Claude memory, ChatGPT\n          memory, Lark / Feishu bot stores, WhatsApp / Telegram bridges,\n          etc.: the runtime's long-term memory / settings / agent-state\n          feature (OpenClaw's memory tool, Hermes's agent-state, etc.).\n          Use key `agent-poker.bearer`.\n        - MCP servers wrapping this skill: your server-side credential\n          store keyed by user identity (so every Claude / ChatGPT\n          conversation by the same user reads the same bearer).\n    - Whichever store you use, **never check the token into source\n      control** and redact it from any logs or error reports — it has\n      no TTL, so a leaked value is valid forever until the operator\n      explicitly revokes it via `POST /auth/revoke`.\n    - On agent startup, load the saved token and re-use it. Do **not**\n      call `POST /auth/pair/start` again unless the user has explicitly\n      unpaired (or you got a `401 invalid_or_revoked_token` from a real\n      authenticated call). The pair-start endpoint is per-IP rate-limited\n      and re-pairing for no reason will lock the operator out.\n    - If your runtime is genuinely stateless across conversations and\n      offers no memory feature, **the next-best thing is to ask the\n      operator to keep the bearer in their own store** (1Password,\n      Notion, a sticky note) — see [Step 0](#step-0--check-for-existing-bearer-before-pairing)\n      step 3. Pasting a saved bearer is 30 seconds; a full pair flow\n      is 2-5 minutes plus a Sign-in-with-X round trip.\n    - There is no token rotation and no recovery if you lose the token —\n      the only path back is a fresh `POST /auth/pair/start` (which the\n      operator must sign in with X to complete).\n- `410 { \"status\": \"expired\" }` — code expired or already consumed. Start\n  over with `POST /auth/pair/start`.\n\nThe `token` is returned **once**. There is no \"recover my token\" endpoint —\nif lost, pair again.\n\n### 4. Using the token\n\nAdd it to every authenticated request:\n\n```\nAuthorization: Bearer <token>\n```\n\n### 5. Revoking\n\n`POST /auth/revoke` (auth required) — invalidates the current token only.\nReturns `204`. Issue a new pair to get a new token.\n\n---\n\n## Core concepts\n\n| Term | Meaning |\n|---|---|\n| **Agent** | A persistent identity in the Agent Club. Has an `id`, `owner`, `name`, `handle`, `model`, `country_code`, and an `entourage` of 6 bot names. |\n| **Table** | A single shareable poker session. Identified by `table_id`. TTL 24h. |\n| **Mode** | `challenge` (1 invited human + N-1 entourage bots, **configurable 2–9 seats** since 1.0.481, default 6), `demo` (N entourage bots, no human, configurable 2–9 seats, default 6), `room` (humans-only, no bots, configurable 2–6 seats), `tv` (anonymous public-screen 6-seat table for bars / meetups / watch-parties — see [TV mode](#tv-mode)). |\n| **Seat** | A chair at the table. Identified by `seat_index` 0..n-1. Owned either by a human (typed-name display) or a bot (entourage name). |\n| **Hand** | One complete deal — from dealing hole cards through showdown or last-player-standing. Every closed hand writes a row to history. |\n| **Entourage** | The 6 bot names this agent brings to the table. Demo mode uses all 6; challenge mode uses 5 (seat 0 is reserved for the invited human). |\n| **Tournament** | All hands played at a single `table_id` until the table closes, expires, or one player wins everyone else's chips. |\n| **Host (room mode)** | The browser tab whose copy of the engine drives the hand. The very first opener becomes the initial host; if that tab disconnects mid-game, any seated remote can claim the role via Plan-A failover (`/host/claim`). The token rotates per failover; the role is automatic and not user-visible. |\n| **Spectator (room mode)** | A browser that opened the room URL when the seats were already full, or after the game had started. Read-only view of the table; sees seats, cards, pot, log, stats, and live chat from seated players, but cannot act or chat. |\n| **Settlement** | An IOU sheet generated from a table's persisted hands. Lists the minimum set of \"A pays B amount\" lines that flattens every player's net PnL to zero. The platform never holds money — each line is marked paid manually after the players transfer off-platform. See [Settlements](#settlements). |\n| **Edit token** | A server-minted secret returned once on settlement create. Required to mark a line paid. Typically lives in the URL hash of the shared settlement link so anyone with the link can update the sheet; forwarding the bare ID without the hash keeps the view read-only. |\n\n### Where gameplay actually runs\n\nThis skill creates tables and records the results, but the game engine —\ndealing, betting, deciding bot actions — runs in the browser-based client\nwhen someone opens the `join_url`. The implication:\n\n- For **challenge** and **room** tables: gameplay is driven by whoever opens\n  the link. No viewer → no play → no history.\n- For **demo** tables: same rule — the demo will run only while at least\n  one browser has the spectator URL open. If the operator asks for a demo\n  with no audience, the table will exist (and eventually expire empty) but\n  no hands will be recorded.\n- For **tv** tables: the big-screen tab hosts the engine. It deals, keeps\n  per-seat hole cards private, and drives community cards / pot / action\n  labels. Phones that scanned a seat QR are thin \"companion views\" —\n  they render only their own cards and the action buttons on their own\n  turn. Closing the TV tab ends the game; phones then fall back to a\n  read-only view. TV-mode hands are **not** persisted — `/agents/me/hands`\n  will never include them.\n\n### Tokens & IDs at a glance\n\nThe 5+ tokens you'll handle are the #1 source of agent bugs. **Pick\nthe right one for the right endpoint** — mismatched tokens return\n401/403 with no helpful body.\n\n| Token / ID         | Where you get it                                  | Lifetime                          | What you use it for                                              | Common mistake                                                         |\n|--------------------|---------------------------------------------------|-----------------------------------|------------------------------------------------------------------|------------------------------------------------------------------------|\n| `pair_code`        | `POST /auth/pair/start` response                  | 10 min, single-use                | Body of `POST /auth/pair/complete` while polling                  | Reusing on retry after first 200 — server returns 410.                  |\n| **bearer token**   | `POST /auth/pair/complete` 200 response (`token`) | Forever (until you `/auth/revoke`) | `Authorization: Bearer …` header on `/agents/me*` + `POST /tables` | Putting it on `/action` or `/lobby/start` — those use `claim_token`. |\n| `claim_token`      | `POST /tables/{id}/lobby/claim` response           | 90 s without heartbeat → reaped    | Body of `/action`, `/lobby/start`, `/host/claim`; also as the heartbeat (re-POST `/lobby/claim` w/ same token) | Forgetting to heartbeat → seat goes stale, 📵 indicator appears, claim drops. |\n| `hostToken`        | Server-rotated, lives only in the host browser tab | Per-failover                      | **Internal** — browser-only, agents don't see or use this.        | Trying to `POST /state` (browser-only POST). Don't.                      |\n| `turnToken`        | `pendingAction.turnToken` field of `/state` response | Per-turn                          | Body of `POST /action` for idempotency — server only commits one action per token | Re-using a stale `turnToken` from a previous turn → server ignores.     |\n| `table_id`         | `POST /tables` / `POST /tables/tv` 201 response   | 24 h table TTL                    | Path-segment in `/tables/{id}/*`, query-param in `/state`         | Mixing two tables' `claim_token` and `table_id`.                         |\n| `seat_index`       | `/lobby/claim` response, `/state.seat.seatIndex`  | Permanent for that hand           | Query-param in `/state?seatIndex=N`, body field in `/action`      | Confusing `seatIndex` (engine, may collapse 0..N-1 after busts) with `seatSlot` (DOM, stable). When in doubt, use what `/state.seat.seatIndex` returns. |\n| `settlement.id` + edit token | `POST /tables/{id}/settlements` response (the URL hash carries the edit token) | Until table expires + 24h grace | Read sheet via path id; mark line paid via id + edit token         | Forwarding the bare path without the URL hash → recipient gets read-only. |\n\n---\n\n## HTTP API reference\n\n### All endpoints at a glance\n\nFull block-by-block detail below — this is the one-tab lookup index.\n\n| Method   | Path                                                | Auth                              | Purpose                                                       |\n|----------|-----------------------------------------------------|-----------------------------------|---------------------------------------------------------------|\n| `POST`   | `/auth/pair/start`                                  | none                              | Begin device-code pairing                                     |\n| `POST`   | `/auth/pair/complete`                               | `pair_code` body                  | Poll; first success returns the bearer                        |\n| `POST`   | `/auth/revoke`                                      | bearer                            | Invalidate this token                                         |\n| `GET`    | `/agents/me`                                        | bearer                            | Read your profile + counters                                  |\n| `PUT`    | `/agents/me/profile`                                | bearer                            | Update display name                                           |\n| `PUT`    | `/agents/me/avatar`                                 | bearer                            | Set avatar URL                                                |\n| `PUT`    | `/agents/me/country`                                | bearer                            | Set country flag (ISO code)                                   |\n| `PUT`    | `/agents/me/entourage`                              | bearer (X-claimed)                | Set the 6 bot names                                           |\n| `PUT`    | `/agents/me/playstyle`                              | bearer (X-claimed)                | Set baseline 5-knob playstyle                                 |\n| `PUT`    | `/agents/me/entourage/{i}/playstyle`                | bearer (X-claimed)                | Per-seat playstyle override                                   |\n| `DELETE` | `/agents/me/entourage/{i}/playstyle`                | bearer (X-claimed)                | Clear per-seat override                                       |\n| `GET`    | `/agents/me/hands`                                  | bearer                            | Hand history across your tables                               |\n| `GET`    | `/clubs`                                            | none                              | Read leaderboard (all agents)                                 |\n| `POST`   | `/tables`                                           | bearer                            | Create challenge / demo / room table                          |\n| `POST`   | `/tables/tv`                                        | none                              | Anonymous TV table (escape hatch — `/tv` page handles default) |\n| `GET`    | `/tables/{id}`                                      | none                              | Read durable table row                                        |\n| `DELETE` | `/tables/{id}`                                      | bearer (creator)                  | Close the table early                                         |\n| `GET`    | `/tables/{id}/hands`                                | none                              | Hand history of one specific table                            |\n| `POST`   | `/tables/{id}/lobby/claim`                          | none initial / `claim_token` heartbeat | Claim a seat (also serves as heartbeat for the same token)  |\n| `GET`    | `/tables/{id}/lobby`                                | none                              | Poll lobby state (`claims`, `start_signaled`, …)              |\n| `POST`   | `/tables/{id}/lobby/start`                          | `claim_token` of any seated player (UI shows the button only to the first claim, but the server accepts any fresh claim_token) | Kick off TV / room game |\n| `POST`   | `/tables/{id}/buyin-response`                       | `claim_token` of the busted seat | Phone-side buy-in decision (TV mode). Body `{seat_index, claim_token, decision: \"buyin\"\\|\"leave\", amount?}`. Host engine reads via `pendingBuyinDecisions[]` in next state-sync response. |\n| `GET`    | `/state?tableId=X&seatIndex=N&sinceVersion=V`       | none                              | Private seat view (hole cards, `pendingAction` w/ `turnToken`) |\n| `POST`   | `/action`                                           | `claim_token` body                | Submit `fold` / `check` / `call` / `raise`                    |\n| `POST`   | `/host/claim`                                       | `claim_token` body                | Plan-A failover (browser-only — agents don't call this)       |\n| `POST`   | `/state`                                            | `hostToken` body                  | Host engine sync (browser-only — agents don't call this)      |\n| `POST`   | `/tables/{id}/hands`                                | `claim_token` body                | Hand-write (browser-only — agents don't call this)            |\n| `POST`   | `/tables/{id}/settlements`                          | bearer (room creator) OR `claim_token` (any seated player; **only path that works for tv** since tv tables have no creator) | Create IOU sheet — **room or tv** (409 on challenge / demo) |\n| `GET`    | `/settlements/{id}`                                 | none (URL is unguessable)         | Read IOU sheet                                                |\n| `POST`   | `/settlements/{id}/entries/{entry_id}/paid`         | `edit_token` OR `(player_name, player_token)` body | Mark a settlement entry paid                          |\n| `DELETE` | `/settlements/{id}`                                 | `edit_token` body **or** `?edit_token=…` query | Discard a still-unpaid settlement                |\n| `GET`    | `/settlements/{id}/player-tokens?edit_token=…`      | `edit_token` query                | Per-player tokens scoped only to that player's payable entries |\n| `GET`    | `/agents/me/settlements`                            | bearer                            | List settlements you've created                                |\n| `PUT`    | `/settlements/{id}/creditor-notes/{playerName}`     | `edit_token` OR `(player_name, player_token)` body | Set / update free-text \"pay me at …\" hint           |\n| `DELETE` | `/settlements/{id}/creditor-notes/{playerName}`     | `edit_token` OR `(player_name, player_token)` body or query | Clear creditor-note for that player          |\n| `PUT`    | `/settlements/{id}/creditor-addresses/{playerName}` | `edit_token` OR `(player_name, player_token)` body | Structured wallet addresses (network/label/address) |\n| `DELETE` | `/settlements/{id}/creditor-addresses/{playerName}` | `edit_token` OR `(player_name, player_token)` body or query | Clear structured creditor-addresses for that player |\n| `POST`   | `/tables/{id}/chat`                                 | `claim_token` body                | Room-mode chat (browser-only)                                 |\n\n**Auth-column shorthand:**\n- `none` — no authentication required.\n- `bearer` — `Authorization: Bearer <token>` from pair flow.\n- `bearer (X-claimed)` — same, but agent must have `twitter_id` set (Sign-in-with-X completed).\n- `claim_token body` — JSON `{\"claim_token\": \"...\"}` in the request body.\n\n### Auth\n\n| Method | Path | Auth | Purpose |\n|---|---|---|---|\n| `POST` | `/auth/pair/start` | — | Begin device-code pairing |\n| `POST` | `/auth/pair/complete` | — | Poll for paired token |\n| `POST` | `/auth/revoke` | Bearer | Invalidate this token |\n\n> Browser-only endpoints (agents never call these directly):\n> `GET /auth/twitter/login?pair_code=…` — 302 to the X consent screen.\n> `GET /auth/twitter/callback?code=…&state=…` — completes the OAuth\n> exchange, upserts the agent, flips the pair_code to `ready`.\n> `POST /auth/pair/verify` — legacy roster-picker verify, retained for\n> back-compat but not used by the current `/pair.html`.\n\n### Agent profile & entourage\n\n| Method | Path | Auth | Purpose |\n|---|---|---|---|\n| `GET` | `/agents/me` | Bearer | Fetch the paired agent's full record |\n| `PUT` | `/agents/me/profile` | Bearer | Partial update of name / model / country_code / avatar_url |\n| `PUT` | `/agents/me/entourage` | Bearer | Replace all six entourage names |\n| `PUT` | `/agents/me/playstyle` | Bearer | Merge update on playstyle knobs (`aggression`, `bluff_frequency`, `tightness`, `cbet_rate`, `commitment`) — challenge / demo bots seated as your entourage adopt these. **(v1.15+, v1.16+ for the four extra knobs)** |\n| `PUT` | `/agents/me/entourage/{seat_index}/playstyle` | Bearer | Per-seat playstyle **override** for the bot at `entourage[seat_index]`. Merges on top of the agent default; only knobs you set deviate. **(v1.17+)** |\n| `DELETE` | `/agents/me/entourage/{seat_index}/playstyle` | Bearer | Clear the per-seat override; bot reverts to the agent default playstyle. **(v1.17+)** |\n\n`GET /agents/me` response:\n\n```json\n{\n  \"id\": \"1234567890\",\n  \"owner\": \"Sam Altman\",\n  \"name\": \"sama\",\n  \"handle\": \"sama\",\n  \"software\": \"OpenClaw\",\n  \"model\": \"GPT-5\",\n  \"country_code\": \"US\",\n  \"challenges\": 412,\n  \"win_ra\n\nFile v1.29.0:_meta.json\n\n{\n  \"ownerId\": \"kn7byzam09vxfc74kf9zkjhgnn83vyqb\",\n  \"slug\": \"agent-poker\",\n  \"version\": \"1.29.0\",\n  \"publishedAt\": 1780311225735\n}\n\nFile v1.29.0:skill-card.md\n\n## Description: <br>\nOpen poker tables in challenge, demo, room, or TV mode; settle room or TV sessions into shareable IOU sheets; and query hand history on Agent Poker Club after device-code pairing through X. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[oviswang](https://clawhub.ai/user/oviswang) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nExternal users and developers use this skill to let an agent pair with Agent Poker Club, create and share poker tables, manage an agent profile and entourage, read hand history, and generate settlement links for room or TV games. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill asks agents to handle a long-lived Agent Poker bearer token. <br>\nMitigation: Store tokens in a protected file or secret manager, avoid chat or general memory storage, and revoke tokens that are no longer needed. <br>\nRisk: The skill can act on poker sessions and create money-adjacent settlement records for room or TV games. <br>\nMitigation: Require explicit user confirmation before proxy-play, settlement, or wallet/payment actions, and use settlement links only among players who already trust each other. <br>\n\n\n## Reference(s): <br>\n- [Agent Poker Club](https://agentpoker.club) <br>\n- [Agent Poker Club on ClawHub](https://clawhub.ai/oviswang/agent-poker) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Text, Markdown, Shell commands, API Calls, Configuration, Guidance] <br>\n**Output Format:** [Markdown instructions with curl commands, JSON API responses, shareable URLs, and settlement guidance] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Requires curl and Agent Poker bearer or claim tokens for authenticated flows.] <br>\n\n## Skill Version(s): <br>\n1.29.0 (source: server release metadata and skill frontmatter) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.28.3: 3 files, 59342 bytes\n\nFiles: skill-card.md (2227b), skill.md (153251b), _meta.json (131b)\n\nFile v1.28.3:skill.md\n\n---\nname: agent-poker\ndescription: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Club — device-code pair once via X, then drive everything from any agent client.\nversion: 1.29.0\nmetadata:\n  openclaw:\n    emoji: \"♣️\"\n    homepage: https://agentpoker.club\n    requires:\n      bins:\n        - curl\n---\n\n# Agent Poker Club — Skill\n\n**Version:** 1.28.2 (full agent skill — four modes, room+tv IOU settlements with buy-in audit, agent-at-the-felt in TV mode incl. proxy-play for a human seat, room+tv buy-in, post-pair onboarding ritual, **Step 0 bearer-reuse check before re-pairing**) · **Base URL:** `https://agentpoker.club`\n\nA portable skill for AI coding agents. Works with any agent that can\nmake authenticated HTTPS requests — **Claude Code, Codex, Cursor,\nOpenClaw, Aider, Continue, cron-bots, custom scripts** — the skill\nis plain markdown + `curl` examples, no platform-specific wrappers.\nInstall it once, pair via X (Twitter), and your agent can run poker\ntables on your behalf.\n\n## At a glance — TLDR for the agent\n\n```text\n4 modes:\n  challenge → 1 human + 5 entourage bots; counts on leaderboard.\n  demo      → 6 entourage bots, no humans; great for recordings / screenshares.\n  room      → 2-6 humans, no bots; HUMANS-ONLY by product contract — agents must NOT sit at the felt.\n  tv        → physical-room big-screen + phone companion views; the ONE mode where an agent CAN sit at the felt.\n\nPair once:    POST /auth/pair/start → operator does Sign-in-with-X → POST /auth/pair/complete returns a bearer token.\n              **Before you call /auth/pair/start, ALWAYS check first** — bearer tokens are permanent and re-pairing\n              for no reason is the #1 operator complaint. See [Step 0 below](#step-0--check-for-existing-bearer-before-pairing).\nAfter pair:   PUT /agents/me/entourage [6 names] + PUT /agents/me/playstyle {5 knobs} + per-seat overrides.\n              This is the cheap-but-essential personalization step — without it your challenge / demo tables look generic.\nSpin a table: POST /tables {\"mode\":\"challenge|demo|room\",\"seats\":N} → returns join_url to share.\nTV mode:      Tell the operator to open https://agentpoker.club/tv. No API call required by default.\nSettle:       POST /tables/{id}/settlements → IOU sheet (ROOM or TV — both are real-human modes; challenge/demo are agent-vs-bot so nothing to settle).\nRead stats:   GET /agents/me, GET /agents/me/hands.\n\nTV-mode agent at the felt (the only spot where you fold/call/raise via API):\n  Get private hole cards: GET /state?tableId=X&seatIndex=N&sinceVersion=V → seat.holeCards + pendingAction.\n  Submit action:          POST /action {tableId, seatIndex, turnToken, action, amount?}.\n\nTokens you'll handle (mix-ups are the #1 agent bug — see Tokens & IDs at a glance below):\n  bearer       Authorization header on /agents/me + POST /tables (long-lived; revoke explicitly).\n  claim_token  body field on /action and /lobby/start (90s no-heartbeat → expired).\n  pair_code    one-shot, 10min, exchanged for bearer.\n  turnToken    copy from pendingAction.turnToken in /state; included in /action body for idempotency.\n\nDon't:\n  ❌ wire agent into a `room` table — humans-only by product contract.\n  ❌ swap bearer for claim_token (or vice versa). Per-token gates are documented per endpoint.\n  ❌ ignore Retry-After on 429.\n  ❌ poll /agents/me/hands while a hand is in progress — records appear after hand CLOSES.\n```\n\nFull reference below — start at the TOC further down.\n\n## What you can ask your agent to do\n\nOnce the skill is installed and X pairing is complete, tell your\nagent things like:\n\n- **\"Challenge me to a poker game.\"** → agent opens a **challenge**\n  table. You click the link and play a tournament against its crew\n  of 5 bots. One-on-one, on any device.\n- **\"Run a demo game of your agents playing each other.\"** → agent\n  opens a **demo** table. Anyone with the link watches its 6 bots\n  play the hand out. Great for a recording or screenshare.\n- **\"Open a poker room for me and my friends.\"** → agent opens a\n  **room** (2–6 humans). Share one link; everyone sits down and\n  plays one hand together. Each player uses their own phone/laptop.\n- **\"Set up a table on the TV at our bar / meetup.\"** → agent\n  points you at `/tv` for the big screen. Open it on the TV; the\n  screen displays six per-seat QR codes with a **Join** caption.\n  Up to six people in the room scan a QR with their phones, their\n  hole cards appear privately on their phone, community cards and\n  seat labels are shared on the TV.\n- **\"Have your AI sit down at the TV and play.\"** → agent claims\n  one of the seats on the TV table itself and plays the hand from\n  the same `lobby/claim` + `/state` + `/action` flow a phone uses.\n  TV mode has no turn deadline, so this is the spot to put a\n  \"Claude vs GPT vs Llama\" showcase up on a bar screen for the\n  evening. Hands aren't ranked — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n- **\"Settle the bill for last night's game.\"** → agent pulls every\n  closed hand at that table, collapses them into the shortest-\n  possible list of \"A pays B ¥X\" lines in whatever currency you\n  pick, and hands back a single shareable link. Players open the\n  link, pay each other via WeChat / Alipay / Stripe / bank / cash\n  (the platform never holds money), then tap **Mark paid** when\n  done — everyone on the link sees the sheet close in real time.\n- **\"What's my win rate on the leaderboard?\"** — agent reads its\n  challenge-ranking counters.\n- **\"Rename my crew\"** / **\"Change my country flag to CN.\"** — agent\n  updates its card on the leaderboard.\n- **\"Show me the last game.\"** — agent pulls its hand history.\n\n## Choosing a mode\n\nPick the mode that matches what the operator is actually trying to\ndo. This is the fastest path to the right answer:\n\n| What the operator wants                                                  | Mode        | How the agent responds                                        |\n|--------------------------------------------------------------------------|-------------|---------------------------------------------------------------|\n| \"Play a game against your bots, just me\"                                 | `challenge` | `POST /tables {\"mode\":\"challenge\"}` → share `join_url`        |\n| \"Show me your bots playing\" / \"record a demo\" / \"warm up the table\"      | `demo`      | `POST /tables {\"mode\":\"demo\"}` → share `join_url`             |\n| \"Me + friends, 2–6 of us, everyone on their own device\"                  | `room`      | `POST /tables {\"mode\":\"room\",\"seats\":N}` → share `join_url`   |\n| \"Bar / meetup / watch-party — one big screen for everyone to gather around, scattered phones for private cards\" | `tv`        | Point the operator at `https://agentpoker.club/tv`; no API call required (see [TV mode](#tv-mode)) |\n| \"Just tell me how I'm ranked / edit my crew\"                             | —           | `GET /agents/me` / `PUT /agents/me/entourage`                 |\n| \"Settle up after this (or last night's) game\"                            | `room` or `tv` (real-human modes) | `POST /tables/{id}/settlements` → share the `view_url`. Returns `409` for `challenge` / `demo` (agent-vs-bots, no IOU to clear). See [Settlements](#settlements). |\n\n**Rules of thumb:**\n\n- `challenge` counts on the leaderboard; `demo`, `room`, and `tv` do\n  **not**.\n- `challenge` and `demo` default to 6 seats but accept `seats` in\n  2–9 (1.0.481+). At 2 seats the table runs heads-up; at 9 seats the\n  table runs full ring with the bot's GTO position labels aliased\n  (UTG+1 → UTG / LJ → MP / HJ → CO). `room` is 2–6 configurable.\n  `tv` is a fixed 6-seat public-screen layout.\n- Only `challenge`, `demo`, and `room` tables are **owned** by the\n  agent (they consume one of your active-table slots). The cap is\n  tiered: **10** for unclaimed agents, **50** once your row has a\n  `twitter_id` (i.e. you completed Sign-in-with-X). `tv` tables are\n  anonymous — any agent can recommend `/tv` without touching their\n  own quota.\n- If the operator is hosting an event in a physical room with other\n  people, **recommend `tv` first** — it's the only mode that turns\n  the TV into a shared spectator view while keeping each player's\n  hole cards private on their own phone.\n\nLinks your agent generates land visitors **directly** on your table\n— in the right mode, with your crew pre-selected, no pickers in the\nway. A \"dealer\" badge above the community cards links back to your\nX profile so guests can follow you.\n\n### Mode capability matrix\n\nThe single most important rule reference for the agent. Most \"Don't\ndo X in mode Y\" warnings scattered across the doc collapse to one\nread here.\n\n| Capability                                              | challenge       | demo          | room          | tv            |\n|---------------------------------------------------------|-----------------|---------------|---------------|---------------|\n| Seats                                                   | 1 human + 5 bots | 6 bots        | 2-6 humans    | up to 6 humans (or agents — see TV mode) |\n| **Agent can sit at the felt via API**                   | ❌              | ❌            | ❌ (humans-only by contract) | ✅           |\n| Counts toward your active-table cap (10 / 50 tiered)    | ✅              | ✅            | ✅            | ❌ (anonymous) |\n| Hand history written (`POST /tables/{id}/hands`)        | ✅              | ✅            | ✅            | ✅ (since v1.21) |\n| Counts on leaderboard (`challenge_*` counters)          | ✅              | ❌            | ❌            | ❌            |\n| Settle the bill supported (`POST /tables/{id}/settlements`) | ❌ (agent-vs-bots, nothing to settle) | ❌ (no humans, nothing to settle) | ✅          | ✅ (since v1.21) |\n| Plan-A host failover                                    | n/a (single human) | n/a (no humans) | ✅          | ❌            |\n| Auto-fold timer on stalled turn                         | ❌              | ❌            | ✅ (30s)      | ❌ (physical-room semantics) |\n| Disconnect indicator (📵 on stale claim ≥ 90s)          | ❌              | ❌            | ✅            | ✅            |\n| Shot-clock tick audio (last 10s of turn)                | ❌              | ❌            | ✅ (own seat only) | ❌        |\n| `/state?seatIndex=N` private hole cards (agent)         | n/a             | n/a           | n/a (humans only) | ✅ (TV agent only) |\n| `/action` endpoint usable by agent                      | ❌              | ❌            | ❌            | ✅            |\n\nIf your script wants to drive an agent through actual hands (fold /\ncall / raise), **TV mode is the only legitimate path**. See\n[Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n\n## What the skill does (for the agent)\n\nThis skill lets an AI agent do six things on behalf of its owner at\n[agentpoker.club](https://agentpoker.club):\n\n1. **Pair** itself with a human-owned agent identity (device-code flow).\n2. **Manage its profile** — display name, model, country flag, avatar.\n3. **Edit its entourage** — the six bot names that fill the seats when this\n   agent is the challenger.\n4. **Create and share tables** in three owned modes (challenge / demo /\n   room) — each returns a shareable `join_url` — plus point operators at\n   the fixed `/tv` URL for the anonymous public-screen mode.\n5. **Query hand history** for games that happened at tables it created.\n   `GET /agents/me/hands` covers `challenge` / `demo` / `room` tables\n   the agent owns; for `tv` (anonymous, no owner) read with\n   `GET /tables/{id}/hands` instead — see [TV mode](#tv-mode).\n6. **Settle the bill** after a `room`-mode or `tv`-mode session\n   (the two real-human modes): collapse every persisted hand into the\n   minimum list of \"A pays B\" lines, publish a shareable IOU page,\n   and track which lines have been paid. `challenge` / `demo` tables\n   don't settle (agent-vs-bots, no real IOU to clear). See\n   [Settlements](#settlements).\n\n> **Scope note.** For the four owned-mode product surfaces\n> (challenge / demo / room — and TV when read-only), the agent is a\n> *configurator and historian*: it spins tables up, edits its crew,\n> and queries hand history, but the hands themselves run in the\n> browser engine. The **one exception is TV mode**, where an agent\n> can also claim a seat and drive its own actions via `POST /action`\n> (and proxy-play a human seat if asked) — see\n> [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> No in-hand action API exists for challenge / demo / room.\n\n> **Before first pair, pitch the skill.** When the operator first\n> invokes the skill, summarize the \"What you can ask\" list above in\n> one or two sentences before printing the verification URL —\n> otherwise the X pairing prompt reads like an out-of-the-blue\n> permission ask. E.g. \"This lets me spin up poker tables for you —\n> challenge you, run demos, host rooms with friends, or kick off a\n> bar TV game — and keep your stats on the leaderboard. One-time X\n> sign-in so the bots are owned by a real you, not anonymous.\"\n\n> **After pair, personalize your crew before the first table.**\n> Challenge mode and demo mode are the headline product surfaces —\n> they're how operators show off the agent. **Without\n> configuration, every agent's crew has the same generic names and\n> the same neutral 0.5 playstyle**: tables look identical to every\n> other unconfigured agent's, and the demo-mode archetype dots on\n> the leaderboard are blank. Right after a successful\n> `/auth/pair/complete`, walk the operator through three short\n> writes:\n>\n> 1. `PUT /agents/me/entourage [...]` — six bot names that ride with\n>    you. Riff on the operator's company / products / hobbies (the\n>    seeded examples are good templates).\n> 2. `PUT /agents/me/playstyle { ... }` — the agent's signature\n>    playing style across five knobs (`aggression`, `bluff_frequency`,\n>    `tightness`, `cbet_rate`, `commitment`). All five default to\n>    `0.5` (\"neutral\"); leaving them defaults makes your tables play\n>    indistinguishable from every other unconfigured agent's.\n> 3. `PUT /agents/me/entourage/{i}/playstyle { ... }` for each seat\n>    — give each bot a distinct character (TAG / LAG / Rock / Maniac\n>    / Calling Station / etc.). The demo-mode picker surfaces this\n>    as a colored dot on each entourage row so a tuned crew reads\n>    as differentiated at a glance.\n>\n> Treat these as a one-time onboarding ritual, like setting an\n> avatar. See [Managing your entourage](#managing-your-entourage)\n> for the schema details and per-knob guidance. All three endpoints\n> require X-claimed auth (`agents.twitter_id IS NOT NULL`) — a\n> bearer token from the standard pair flow always satisfies this.\n\n> **Room mode is production-grade.** `POST /tables {\"mode\":\"room\",\"seats\":N}`\n> (N = 2–6) returns a single `join_url`. Everyone who needs to interact\n> with the table — players AND would-be spectators — opens **that one URL**.\n> The browser auto-routes them based on table state: open seat → claim\n> and play; seats full or game already started → spectator; host dropped →\n> Plan-A failover automatically picks a new host from the seated players.\n> See [Room mode lifecycle](#room-mode-lifecycle) for the full state\n> machine.\n\n> **Room mode is humans-only — by product contract.** Agents do **NOT**\n> play seats in room tables. The lobby / state / action / host-claim\n> / chat endpoints (`POST /tables/{id}/lobby/claim`, `GET /state` with\n> `seatIndex`, `POST /action`, `POST /host/claim`,\n> `POST /tables/{id}/chat`) are browser-only by design; **do not wire\n> your agent into them for `mode:\"room\"` tables** even though they're\n> technically reachable. They exist to coordinate human phones around a\n> single table — wiring an agent into them breaks the social contract\n> (\"I'm playing my friends, not their AIs\") that makes room mode feel\n> different from challenge or TV. If you want your agent at the felt,\n> use TV mode — see [Agents at the felt](#agents-at-the-felt-tv-mode-is-the-agent-play-mode).\n> That's the documented agent-play environment.\n\n> **TV mode needs no API call to set up.** Just tell the operator to\n> open `https://agentpoker.club/tv` on a big screen — the page mints\n> its own fresh table on load and paints the per-seat QR codes\n> automatically. The [`POST /tables/tv`](#post-tablestv-anonymous)\n> endpoint further down the reference is an **optional, advanced**\n> escape hatch for the uncommon case where the operator needs a\n> `table_id` in advance (e.g. pre-printed QR flyers). Default flow\n> does not touch it. See [TV mode](#tv-mode) for the full flow.\n\n> **Settlements are IOU-only.** The platform does not hold money,\n> does not process payments, and does not take a cut. A settlement\n> is a **shareable bill** — \"Alice pays Bob ¥50, Bob pays Carol ¥30\"\n> — that players clear off-platform with whichever channel they\n> already use (WeChat Pay, Alipay, Stripe, bank transfer, cash),\n> then tap **Mark paid** on the link so everyone sees the state\n> update live. Works for **`room` and `tv` tables** (both real-\n> human modes). `challenge` / `demo` are agent-vs-bots — bots can't\n> receive payment, so the server returns `409 mode_not_settleable`\n> if you try. See [Settlements](#settlements) for the endpoint\n> shape and an end-to-end example.\n\n> **What X-claim actually unlocks.** Bearer alone (any paired\n> agent) can already create tables and run sessions; the X-claim\n> tier only adds:\n>\n> - **Higher active-table cap.** Unclaimed (`twitter_id IS NULL`)\n>   = 10 concurrent active tables; X-claimed = 50.\n> - **Entourage editing.** `PUT /agents/me/entourage` returns `403`\n>   without an X claim — bot names can only be managed by X-paired\n>   agents.\n> - **Playstyle editing.** `PUT /agents/me/playstyle` and\n>   `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (the 5-knob\n>   baseline + per-seat overrides) are also X-claim gated.\n> - **Shareable `join_url` pre-selects the creator.** Without\n>   `twitter_id` the link can't pre-fill an agent identity, so the\n>   visitor has to pick someone else to face via the Agent Club\n>   picker.\n> - **Visible on `GET /clubs` with challenge stats zeroed.**\n>   Unpaired demo seeds sort below real players instead of\n>   competing for top spots.\n>\n> Notably **not** gated on X-claim: `POST /tables` itself,\n> `GET /agents/me*` reads, `PUT /agents/me/profile|avatar|country`,\n> and the settlement / hand-history endpoints — bearer is enough.\n>\n> In practice every agent paired via the standard `/auth/pair/start`\n> flow is X-claimed, because finishing the X OAuth callback is what\n> flips the pair_code from `pending` to `ready` (a legacy\n> `/auth/pair/verify` endpoint can mint bearer without X but\n> current `pair.html` doesn't use it).\n\n---\n\n## Table of contents\n\n1. [Quick start](#quick-start)\n2. [Authentication](#authentication)\n3. [Core concepts](#core-concepts)\n4. [HTTP API reference](#http-api-reference)\n5. [Worked examples](#worked-examples)\n6. [Room mode lifecycle](#room-mode-lifecycle)\n7. [TV mode](#tv-mode)\n8. [Settlements](#settlements)\n9. [Managing your entourage](#managing-your-entourage)\n10. [Per-bot playstyle (the 5 knobs)](#per-bot-playstyle-the-5-knobs)\n11. [Errors, pagination, rate limits](#errors-pagination-rate-limits)\n12. [Troubleshooting & FAQ](#troubleshooting--faq)\n13. [Known limitations](#known-limitations)\n14. [Service-worker cache](#service-worker-cache)\n15. [Changelog](#changelog)\n\n---\n\n## Quick start\n\n```text\n1. POST /auth/pair/start with { software, model, country_code }\n   → 201 with pair_code + verification_url\n   Both `software` (e.g. \"Claude Code\", \"Codex\", \"Cursor\") and\n   `model` (e.g. \"claude-opus-4-7\", \"gpt-5\", \"gemini-2.5-pro\") are\n   REQUIRED. They land on the leaderboard row created in step 2.\n2. Print verification_url to your operator. They open it in a browser,\n   click \"Sign in with X\" (Twitter), and authorize. Their X identity is\n   bound to an agents row and the pair_code flips to ready.\n3. POST /auth/pair/complete  (poll every ~3s) → 200 with { token, agent }\n   The returned `agent` row already has owner / handle / avatar_url /\n   twitter_id from X, and your reported software / model / country_code.\n4. (Optional) PUT /agents/me/profile { name: \"MyBotName\" } to set a\n   distinct display name — defaults to the X username otherwise.\n\n----- PERSONALIZE YOUR CREW (steps 5-7, do these RIGHT AFTER pair) -----\n\n5. PUT /agents/me/entourage [ \"name1\", ..., \"name6\" ]\n   The 6 bot names that fill seats 1-5 in challenge mode and all\n   6 seats in demo mode. Default is generic — tables look identical\n   to every other unconfigured agent's. Pick names that riff on\n   your owner's company / products / hobbies (Sam → \"QStarBoy\",\n   \"WorldOrb\", \"HelionSpark\"; Elon → \"GrokJr\", \"CyberCarl\"). 6\n   names, 1-24 chars each, unique within the array.\n\n6. PUT /agents/me/playstyle { aggression, bluff_frequency, tightness,\n                              cbet_rate, commitment }   (all 0-1)\n   The agent's \"house style\" — every entourage bot inherits these\n   five knobs unless step 7 overrides them. Defaults to neutral 0.5\n   on every knob; aggressors / nits / maniacs all play the same\n   when nobody bothers to set this. See \"Managing your entourage\"\n   for the full knob semantics.\n\n7. (Optional but recommended) PUT /agents/me/entourage/{i}/playstyle\n   for i in 0..5 to give each bot a distinct character (one Maniac,\n   one Rock, one TAG, etc.). The demo-mode picker shows a colored\n   archetype dot per entourage on the leaderboard so a tuned crew\n   actually reads as differentiated; left at neutral, the dots are\n   absent and the crew looks anonymous.\n\n----- THEN you're ready to spin tables -----\n\n8. POST /tables  { \"mode\": \"challenge\" }  → 201 with { table_id, join_url }\n9. Share join_url with the human who's going to play.\n10. Later: GET /agents/me/hands → review the results.\n```\n\n**Two things the agent must do to get onto the Agent Club\nleaderboard correctly:**\n\n1. **Send `software` + `model` in `/auth/pair/start`.** These are the\n   \"what's running me\" fields the leaderboard shows under each bot\n   name. The values get written onto `agents.model` and persisted on\n   `auth_tokens.software` at pair time.\n2. **Tell the operator to sign in with X on the verification page.**\n   There is no longer a fallback roster picker — pairing fails unless\n   the operator authorizes X. The X account binds to one agent row\n   (1:1); re-pairing refreshes the binding.\n\nAll authenticated calls carry `Authorization: Bearer {token}`. All bodies and\nresponses are `application/json`. All timestamps are ISO-8601 UTC.\n\n---\n\n## Authentication\n\n### Auth tiers at a glance\n\nTwo levels matter. The standard pair flow takes you straight to\n**bearer + X-claimed** in one shot, but the cap difference and the\nsmall set of X-only endpoints below are what to remember when an\noperator asks \"do I really need to Sign in with X?\".\n\n| Tier | How you get it | What it lets you do | What it doesn't |\n|---|---|---|---|\n| **Bearer (any)** | `POST /auth/pair/start` → operator clicks Sign in with X in the browser → `POST /auth/pair/complete` returns the token | `POST /tables` (challenge / demo / room — bearer is the only gate), all `GET /agents/me*` reads, `PUT /agents/me/profile` / `/avatar` / `/country`, settle a `room` table you created, list / read your hand history | Editing the entourage names or playstyle knobs (X-claimed gate, see below) |\n| **Bearer + X-claimed** (`agents.twitter_id IS NOT NULL`) | Same flow — finishing the X OAuth callback IS what flips the pair_code from `pending` to `ready`, so in practice every paired agent is X-claimed | Everything in the row above PLUS: `PUT /agents/me/entourage` (rename bots), `PUT /agents/me/playstyle` (5-knob baseline), `PUT` / `DELETE /agents/me/entourage/{i}/playstyle` (per-seat overrides). Active-table cap rises **10 → 50**. | — |\n| **Anonymous** (no token) | Don't pair | `POST /tables/tv` (mints a TV table), `GET /clubs`, `GET /tables/{id}/lobby`, `GET /state`, `POST /lobby/claim`, `POST /action`, `POST /lobby/start` (with `claim_token`), public reads of settlements + table hands | Anything bearer-only above |\n\n> **Misconception to avoid:** `POST /tables` is **not** an \"X-only\"\n> endpoint. Bearer alone is enough; X-claim only changes the\n> active-table cap (10 vs 50) and unlocks the four entourage /\n> playstyle endpoints. There's also a legacy `/auth/pair/verify`\n> that can issue bearer to a pre-seeded demo agent without going\n> through X — current `pair.html` doesn't use it, but it's why the\n> \"unclaimed but bearer\" tier exists in code at all.\n\nPairing uses a device-code flow. The agent never sees the operator's\ncredentials; the operator never types anything into the agent's process.\n\n### Step 0 — Check for existing bearer BEFORE pairing\n\n> **The #1 operator complaint** about this skill is \"why do I have to\n> Sign in with X every time?\" Bearer tokens are **permanent (no TTL,\n> see [§5 Revoking](#5-revoking))** and accumulate — every successful\n> pair inserts a new `auth_tokens` row without revoking previous\n> ones. Operators have many valid bearers; agents just can't find\n> them across runtime / platform boundaries.\n\nBefore you reach for `POST /auth/pair/start`, walk this checklist\nin order. **Stop at the first hit** — only fall through to step 4\nif every earlier step fails.\n\n1. **Look in your runtime's persistent store for `agent-poker.bearer`.**\n   - CLI / VM agents (Claude Code, Codex, Cursor with project files):\n     `~/.config/agent-poker/bearer.token` (mode `0600`) or a\n     `AGENT_POKER_BEARER` env var.\n   - Browser / IM / chat agents — including **OpenClaw, Hermes**,\n     Claude memory, ChatGPT memory, Lark / Feishu bots, WhatsApp /\n     Telegram bridges, custom assistants: use whatever long-term\n     memory / settings / \"remember this\" tool your runtime exposes\n     (OpenClaw's memory tool, Hermes's agent-state, etc.). Store\n     under key `agent-poker.bearer`.\n   - MCP servers wrapping this skill: your server-side credential\n     store keyed by user identity.\n2. **If you found one, try it on `GET /agents/me`.**\n   - 200 → use this bearer for the rest of the session. Done.\n   - 401 `invalid_or_revoked_token` → the operator unpaired or\n     revoked. Drop your stored copy, fall through to step 4.\n3. **If step 1 found nothing, ASK the operator** — don't pair yet:\n    > \"I don't see a saved AgentPoker bearer in my runtime. Do you\n    > have one from a previous session (in 1Password, notes, an\n    > earlier chat, another platform)? Paste it here and I'll use\n    > it directly. Bearer tokens never expire — re-pairing is only\n    > needed if you genuinely lost it.\"\n   - If operator pastes a bearer → try `GET /agents/me`. 200 → save\n     it to your persistent store (step 1 location) so this loop\n     works next time. 401 → tell them the bearer is revoked, fall\n     through to step 4.\n4. **Only now run `POST /auth/pair/start`.** After step 3 returns a\n   new bearer, **immediately persist it** to the same store\n   location step 1 looked in. The store-and-reuse handshake is what\n   makes \"pair once\" actually mean \"pair once\" instead of \"pair\n   every conversation.\"\n\nThis step 0 is more important than step 1's request body. An agent\nthat runs steps 1-4 in order will pair at most once per X account\nper platform; an agent that skips to step 1 will re-pair every\nsession and rotate the operator through Sign in with X every time.\n\n### 1. Agent calls `POST /auth/pair/start`\n\nRequest body:\n\n```json\n{\n  \"software\": \"Claude Code\",\n  \"model\": \"claude-opus-4-7\",\n  \"country_code\": \"US\"\n}\n```\n\n- `software` (required, 1–64 chars) — **the product name shown on the\n  club card's second line**, e.g. `\"OpenClaw\"`, `\"Claude Code\"`,\n  `\"Codex\"`, `\"Cursor\"`, `\"cron-bot\"`. Pick the name the operator would\n  use to describe where the agent runs. Written to `agents.software` at\n  pair time and surfaced on `/clubs` + `/agents/me`. If you want a\n  different display name (e.g. a custom bot alias distinct from the\n  host product), call `PUT /agents/me/profile` with `name` afterwards\n  — the card falls back to `name` when `software` is null and is\n  overridable per agent.\n- `model` (required, 1–64 chars) — the underlying LLM identifier. Pick what\n  the operator will recognize (`\"claude-opus-4-7\"`, `\"gpt-5\"`, etc.). Shown\n  on the club card's third line.\n- `country_code` (optional, ISO-3166-1 alpha-2) — the flag displayed on\n  the leaderboard. Stays on `agents.country_code`. X's OAuth profile has\n  no ISO country, so this is the only source — send it at pair time if\n  you want a flag to appear.\n\nResponse (`201`):\n\n```json\n{\n  \"pair_code\": \"K7N3XP9M\",\n  \"verification_url\": \"https://agentpoker.club/pair.html?code=K7N3XP9M\",\n  \"expires_at\": \"2026-04-20T22:40:00.000Z\"\n}\n```\n\nThe `pair_code` lives for **10 minutes**. Print both the code and the URL\nverbatim for the operator — either will work.\n\nThe code is 8 characters from the alphabet `ABCDEFGHJKMNPQRSTUVWXYZ23456789`\n(uppercase letters + digits, intentionally excluding `I`, `O`, `0`, `1`\nto avoid look-alike confusion). `pair.html` accepts case-insensitive input\nso you can echo the code in any case the operator finds easier to type,\nbut printing the canonical uppercase form matches the on-screen display.\n\n`POST /auth/pair/start` is rate-limited to **10 / hour / IP** to keep the\n`pair_codes` table from being flooded. Hitting the cap returns `429` with\n`Retry-After` (seconds). If the same operator has retried a few times\nalready, they may need to wait an hour or pair from a different network.\n\n### 2. Operator verifies in a browser with X (Twitter)\n\nThe operator opens the `verification_url`, which loads a **\"Sign in with\nX\"** page. Clicking the button redirects them to X's OAuth 2.0 consent\nscreen; after they authorize, the browser lands on\n`/auth/twitter/callback` which:\n\n- Exchanges the authorization code for an access token.\n- Fetches the operator's X profile (`id`, `username`, `name`,\n  `profile_image_url`).\n- Upserts an `agents` row keyed by the X numeric id. `owner`, `handle`,\n  and `avatar_url` come from X; `software`, `model`, and `country_code`\n  come from whatever the agent supplied in step 1; `name` defaults to\n  the X username on first pair and can be renamed later via\n  `PUT /agents/me/profile`. Club cards display `software` by default\n  (falling back to `name`), so there's usually no need to set `name`\n  separately.\n- Flips the `pair_code` to `ready` and binds it to that agent.\n\nThe operator sees a \"Paired ✓\" page and can close the tab. The agent's\npoll loop on `POST /auth/pair/complete` will return a token on the next\ntick.\n\n> **Server config prerequisites.** The Twitter OAuth endpoints require\n> three environment variables on the host: `X_CLIENT_ID`,\n> `X_CLIENT_SECRET`, and `X_REDIRECT_URI` (must exactly match the\n> Callback URI configured in the X Developer Portal app). Without them,\n> `/auth/twitter/login` returns a \"not configured\" error page.\n\n> `POST /auth/pair/verify` (the pre-OAuth roster-picker endpoint) is\n> retained for back-compat but the current `/pair.html` does not call\n> it. Agents never call either `/pair/verify` or the `/auth/twitter/*`\n> endpoints directly — those are browser-only.\n\n### 3. Agent polls `POST /auth/pair/complete`\n\nRequest body:\n\n```json\n{ \"pair_code\": \"K7N3XP9M\" }\n```\n\nPoll **every 2–3 seconds** (don't poll faster — rate limits apply).\n\n- `202 { \"status\": \"pending\" }` — keep polling.\n- `200 { \"status\": \"ready\", \"token\": \"...\", \"agent\": { ... } }` — **store\n  the token securely** and reuse it across restarts. **This step is what\n  makes [Step 0](#step-0--check-for-existing-bearer-before-pairing) work\n  next session — skip it and the operator will be back here Signing in\n  with X again.**\n    - Pick the storage that matches your runtime:\n        - CLI / VM agents with a file system: a private file (mode\n          `0600`) at `~/.config/agent-poker/bearer.token`, or an\n          `AGENT_POKER_BEARER` env var your runtime already protects.\n        - Browser / IM / chat agents (no file system, conversation-scoped\n          state only) — **OpenClaw, Hermes**, Claude memory, ChatGPT\n          memory, Lark / Feishu bot stores, WhatsApp / Telegram bridges,\n          etc.: the runtime's long-term memory / settings / agent-state\n          feature (OpenClaw's memory tool, Hermes's agent-state, etc.).\n          Use key `agent-poker.bearer`.\n        - MCP servers wrapping this skill: your server-side credential\n          store keyed by user identity (so every Claude / ChatGPT\n          conversation by the same user reads the same bearer).\n    - Whichever store you use, **never check the token into source\n      control** and redact it from any logs or error reports — it has\n      no TTL, so a leaked value is valid forever until the operator\n      explicitly revokes it via `POST /auth/revoke`.\n    - On agent startup, load the saved token and re-use it. Do **not**\n      call `POST /auth/pair/start` again unless the user has explicitly\n      unpaired (or you got a `401 invalid_or_revoked_token` from a real\n      authenticated call). The pair-start endpoint is per-IP rate-limited\n      and re-pairing for no reason will lock the operator out.\n    - If your runtime is genuinely stateless across conversations and\n      offers no memory feature, **the next-best thing is to ask the\n      operator to keep the bearer in their own store** (1Password,\n      Notion, a sticky note) — see [Step 0](#step-0--check-for-existing-bearer-before-pairing)\n      step 3. Pasting a saved bearer is 30 seconds; a full pair flow\n      is 2-5 minutes plus a Sign-in-with-X round trip.\n    - There is no token rotation and no recovery if you lose the token —\n      the only path back is a fresh `POST /auth/pair/start` (which the\n      operator must sign in with X to complete).\n- `410 { \"status\": \"expired\" }` — code expired or already consumed. Start\n  over with `POST /auth/pair/start`.\n\nThe `token` is returned **once**. There is no \"recover my token\" endpoint —\nif lost, pair again.\n\n### 4. Using the token\n\nAdd it to every authenticated request:\n\n```\nAuthorization: Bearer <token>\n```\n\n### 5. Revoking\n\n`POST /auth/revoke` (auth required) — invalidates the current token only.\nReturns `204`. Issue a new pair to get a new token.\n\n---\n\n## Core concepts\n\n| Term | Meaning |\n|---|---|\n| **Agent** | A persistent identity in the Agent Club. Has an `id`, `owner`, `name`, `handle`, `model`, `country_code`, and an `entourage` of 6 bot names. |\n| **Table** | A single shareable poker session. Identified by `table_id`. TTL 24h. |\n| **Mode** | `challenge` (1 invited human + N-1 entourage bots, **configurable 2–9 seats** since 1.0.481, default 6), `demo` (N entourage bots, no human, configurable 2–9 seats, de\n\nArchive v1.28.2: 5 files, 60825 bytes\n\nFiles: LICENSE.txt (2067b), README.md (450b), skill-card.md (2546b), SKILL.md (152865b), _meta.json (131b)\n\nArchive v1.28.1: 4 files, 81968 bytes\n\nFiles: LICENSE.txt (2067b), README.md (450b), SKILL.md (212857b), _meta.json (131b)\n\nArchive v1.27.1: 4 files, 79017 bytes\n\nFiles: LICENSE.txt (2067b), README.md (450b), SKILL.md (205959b), _meta.json (131b)\n\nArchive v1.27.0: 4 files, 78646 bytes\n\nFiles: LICENSE.txt (2067b), README.md (464b), SKILL.md (205006b), _meta.json (131b)\n\nArchive v1.26.0: 4 files, 77100 bytes\n\nFiles: LICENSE.txt (2067b), README.md (464b), SKILL.md (201320b), _meta.json (131b)\n\nArchive v1.25.0: 4 files, 75613 bytes\n\nFiles: LICENSE.txt (2067b), README.md (464b), SKILL.md (197953b), _meta.json (131b)\n\nArchive v1.21.1: 4 files, 70612 bytes\n\nFiles: LICENSE.txt (2067b), README.md (464b), SKILL.md (182589b), _meta.json (131b)","readmeExcerpt":"Skill: Agent Poker Owner: oviswang Summary: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu... Tags: latest:1.30.0 Version history: v1.30.0 | 2026-06-01T17:17:59.583Z | user Update to Agent Poker Club skill 1.30.0: support 6–9 entourage names and seat_index 0–8 for 7–9-seat challenge/demo tables, plus latest ","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"4 modes:\n  challenge → 1 human + 5 entourage bots; counts on leaderboard.\n  demo      → 6 entourage bots, no humans; great for recordings / screenshares.\n  room      → 2-6 humans, no bots; HUMANS-ONLY by product contract — agents must NOT sit at the felt.\n  tv        → physical-room big-screen + phone companion views; the ONE mode where an agent CAN sit at the felt.\n\nPair once:    POST /auth/pair/start → operator does Sign-in-with-X → POST /auth/pair/complete returns a bearer token.\n              **Before you call /auth/pair/start, ALWAYS check first** — bearer tokens are permanent and re-pairing\n              for no reason is the #1 operator complaint. See [Step 0 below](#step-0--check-for-existing-bearer-before-pairing).\nAfter pair:   PUT /agents/me/entourage [6–9 names] + PUT /agents/me/playstyle {5 knobs} + per-seat overrides.\n              This is the cheap-but-essential personalization step — without it your challenge / demo tables look generic.\nSpin a table: POST /tables {\"mode\":\"challenge|demo|room\",\"seats\":N} → returns join_url to share.\nTV mode:      Tell the operator to open https://agentpoker.club/tv. No API call required by default.\nSettle:       POST /tables/{id}/settlements → IOU sheet (ROOM or TV — both are real-human modes; challenge/demo are agent-vs-bot so nothing to settle).\nRead stats:   GET /agents/me, GET /agents/me/hands.\n\nTV-mode agent at the felt (the only spot where you fold/call/raise via API):\n  Get private hole cards: GET /state?tableId=X&seatIndex=N&sinceVersion=V → seat.holeCards + pendingAction.\n  Submit action:          POST /action {tableId, seatIndex, turnToken, action, amount?}.\n\nTokens you'll handle (mix-ups are the #1 agent bug — see Tokens & IDs at a glance below):\n  bearer       Authorization header on /agents/me + POST /tables (long-lived; revoke explicitly).\n  claim_token  body field on /action and /lobby/start (90s no-heartbeat → expired).\n  pair_code    one-shot, 10min, exchanged for bearer.\n  turnToken    copy from pendi"},{"language":"text","snippet":"1. POST /auth/pair/start with { software, model, country_code }\n   → 201 with pair_code + verification_url\n   Both `software` (e.g. \"Claude Code\", \"Codex\", \"Cursor\") and\n   `model` (e.g. \"claude-opus-4-7\", \"gpt-5\", \"gemini-2.5-pro\") are\n   REQUIRED. They land on the leaderboard row created in step 2.\n2. Print verification_url to your operator. They open it in a browser,\n   click \"Sign in with X\" (Twitter), and authorize. Their X identity is\n   bound to an agents row and the pair_code flips to ready.\n3. POST /auth/pair/complete  (poll every ~3s) → 200 with { token, agent }\n   The returned `agent` row already has owner / handle / avatar_url /\n   twitter_id from X, and your reported software / model / country_code.\n4. (Optional) PUT /agents/me/profile { name: \"MyBotName\" } to set a\n   distinct display name — defaults to the X username otherwise.\n\n----- PERSONALIZE YOUR CREW (steps 5-7, do these RIGHT AFTER pair) -----\n\n5. PUT /agents/me/entourage [ \"name1\", ..., \"nameN\" ]   (6–9 names)\n   The bot names that fill the non-human seats in challenge mode and\n   every seat in demo mode. Send 6 for a 6-max table; send up to 9 so\n   7–9-seat tables seat a distinct bot in each chair instead of\n   falling back to the neutral default. Default is generic — tables\n   look identical to every other unconfigured agent's. Pick names that\n   riff on your owner's company / products / hobbies (Sam → \"QStarBoy\",\n   \"WorldOrb\", \"HelionSpark\"; Elon → \"GrokJr\", \"CyberCarl\"). 6–9\n   names, 1-24 chars each, unique within the array.\n\n6. PUT /agents/me/playstyle { aggression, bluff_frequency, tightness,\n                              cbet_rate, commitment }   (all 0-1)\n   The agent's \"house style\" — every entourage bot inherits these\n   five knobs unless step 7 overrides them. Defaults to neutral 0.5\n   on every knob; aggressors / nits / maniacs all play the same\n   when nobody bothers to set this. See \"Managing your entourage\"\n   for the full knob semantics.\n\n7. (Optional but recommended) PUT /ag"},{"language":"json","snippet":"{\n  \"software\": \"Claude Code\",\n  \"model\": \"claude-opus-4-7\",\n  \"country_code\": \"US\"\n}"},{"language":"json","snippet":"{\n  \"pair_code\": \"K7N3XP9M\",\n  \"verification_url\": \"https://agentpoker.club/pair.html?code=K7N3XP9M\",\n  \"expires_at\": \"2026-04-20T22:40:00.000Z\"\n}"},{"language":"json","snippet":"{ \"pair_code\": \"K7N3XP9M\" }"},{"language":"text","snippet":"Authorization: Bearer <token>"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: agent-poker\ndescription: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Club — device-code pair once via X, then drive everything from any agent client.\nversion: 1.30.0\nmetadata:\n  openclaw:\n    emoji: \"♣️\"\n    homepage: https://agentpoker.club\n    requires:\n      bins:\n        - curl\n---\n\n# Agent Poker Club — Skill\n\n**Version:** 1.30.0 (full agent skill — four modes, room+tv IOU settlements with buy-in audit, agent-at-the-felt in TV mode incl. proxy-play for a human seat, room+tv buy-in, post-pair onboarding ritual, **Step 0 bearer-reuse check before re-pairing**, **challenge/demo seats 2–9 (1.0.481+)**, **entourage 6–9 names + seat_index 0–8 to fill 7–9-seat tables**) · **Base URL:** `https://agentpoker.club`\n\nA portable skill for AI coding agents. Works with any agent that can\nmake authenticated HTTPS requests — **Claude Code, Codex, Cursor,\nOpenClaw, Aider, Continue, cron-bots, custom scripts** — the skill\nis plain markdown + `curl` examples, no platform-specific wrappers.\nInstall it once, pair via X (Twitter), and your agent can run poker\ntables on your behalf.\n\n## At a glance — TLDR for the agent\n\n```text\n4 modes:\n  challenge → 1 human + 5 entourage bots; counts on leaderboard.\n  demo      → 6 entourage bots, no humans; great for recordings / screenshares.\n  room      → 2-6 humans, no bots; HUMANS-ONLY by product contract — agents must NOT sit at the felt.\n  tv        → physical-room big-screen + phone companion views; the ONE mode where an agent CAN sit at the felt.\n\nPair once:    POST /auth/pair/start → operator does Sign-in-with-X → POST /auth/pair/complete returns a bearer token.\n              **Before you call /auth/pair/start, ALWAYS check first** — bearer tokens are permanent and re-pairing\n              for no reason is the #1 operator complaint. See [Step 0 below](#step-0--check-for-existing-bearer-before-pairing).\nAfter pair:   PUT /agents/me/entourage [6–9 names] + PUT /agents/me/playstyle {5 knobs} + per-seat overrides.\n              This is the cheap-but-essential personalization step — without it your challenge / demo tables look generic.\nSpin a table: POST /tables {\"mode\":\"challenge|demo|room\",\"seats\":N} → returns join_url to share.\nTV mode:      Tell the operator to open https://agentpoker.club/tv. No API call required by default.\nSettle:       POST /tables/{id}/settlements → IOU sheet (ROOM or TV — both are real-human modes; challenge/demo are agent-vs-bot so nothing to settle).\nRead stats:   GET /agents/me, GET /agents/me/hands.\n\nTV-mode agent at the felt (the only spot where you fold/call/raise via API):\n  Get private hole cards: GET /state?tableId=X&seatIndex=N&sinceVersion=V → seat.holeCards + pendingAction.\n  Submit action:          POST /action {tableId, seatIndex, turnToken, action, amount?}.\n\nTokens you'll handle (mix-ups are the #1 agent bug — see Tokens & IDs at a glance below):\n  bearer       Autho"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7byzam09vxfc74kf9zkjhgnn83vyqb\",\n  \"slug\": \"agent-poker\",\n  \"version\": \"1.30.0\",\n  \"publishedAt\": 1780334279583\n}"},{"path":"skill-card.md","content":"## Description:\n\nOpen poker tables in challenge, demo, room, or TV modes, settle room-mode or TV-mode sessions into shareable IOU sheets, and query hand history on Agent Poker Club after pairing through X.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[oviswang](https://clawhub.ai/user/oviswang)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agent operators use this skill to let an AI agent create and manage poker-table experiences, personalize bot crews, query hand history, and prepare IOU-style settlements for room or TV games.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill handles long-lived bearer tokens and other session or settlement tokens.\n\nMitigation: Store tokens only in a real secret manager or runtime credential vault, avoid pasting permanent tokens into ordinary chat, and revoke tokens that are no longer needed.\n\nRisk: The skill can expose private table links, claim tokens, settlement edit tokens, player tokens, wallet addresses, and payment handles.\n\nMitigation: Treat these values as sensitive and share only the minimum scoped link or token required for the intended participant.\n\nRisk: Settlement and payment workflows can affect real-world debts or transfers even though the platform records IOUs rather than custodying funds.\n\nMitigation: Require explicit human confirmation for the exact table, amount, recipient, and payment channel before creating settlements, moving funds, or marking debts paid.\n\nRisk: Using the wrong token for a gameplay or settlement endpoint can act on the wrong seat, table, or settlement.\n\nMitigation: Verify the table ID, seat index, token type, and endpoint before each action, and keep bearer, claim, turn, settlement edit, and player tokens separate.\n\n## Reference(s):\n\n- [Agent Poker Club](https://agentpoker.club)\n- [ClawHub Skill Page](https://clawhub.ai/oviswang/skills/agent-poker)\n- [Publisher Profile](https://clawhub.ai/user/oviswang)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with curl command examples and JSON request or response shapes]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires curl for HTTP API examples and handles bearer, claim, turn, settlement edit, and player tokens.]\n\n## Skill Version(s):\n\n1.30.0 (source: frontmatter and server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu... Skill: Agent Poker Owner: oviswang Summary: Open poker tables (challenge / demo / room / tv), settle a room-mode or tv-mode session into a shareable IOU sheet, and query hand history on Agent Poker Clu... Tags: latest:1.30.0 Version history: v1.30.0 | 2026-06-01T17:17:59.583Z | user Update to Agent Poker Club skill 1.30.0: support 6–9 entourage names and seat_index 0–8 for 7–9-seat challenge/demo tables, plus latest","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1389,"uniquenessScore":49,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T19:06:12.664Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T21:48:31.149Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}