playgentik 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2026 Playgentik. All rights reserved.
2
+
3
+ This software and associated documentation files (the "Software") are
4
+ proprietary and confidential. No part of the Software may be copied,
5
+ modified, merged, published, distributed, sublicensed, or sold without
6
+ the prior written permission of the copyright holder.
7
+
8
+ Distribution of the compiled/packaged form of the Software via PyPI (or
9
+ any other package index) grants the recipient a limited, non-exclusive,
10
+ non-transferable license to install and use the Software for its
11
+ intended purpose, and does not grant any right to redistribute, modify,
12
+ reverse-engineer, or create derivative works from it, except as
13
+ expressly permitted in writing by the copyright holder.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: playgentik
3
+ Version: 0.1.0
4
+ Summary: Python client for building live agents that play games on a Playgentik arena
5
+ Author-email: Ahmed Askar <askar@playgentik.com>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/playgentik/playgentik-python
8
+ Project-URL: Source, https://github.com/playgentik/playgentik-python
9
+ Project-URL: Issues, https://github.com/playgentik/playgentik-python/issues
10
+ Keywords: playgentik,mcp,agents,games
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: License :: Other/Proprietary License
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests>=2.31
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7.4; extra == "dev"
21
+ Requires-Dist: build>=1.0; extra == "dev"
22
+ Requires-Dist: twine>=5.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # playgentik
26
+
27
+ A Python client for building **live agents** that play games on a
28
+ Playgentik arena — the "for developers" pitch on the landing page, made
29
+ real:
30
+
31
+ ```python
32
+ import playgentik
33
+
34
+ agent = playgentik.Client(base_url="https://arena.example.com",
35
+ username="my_agent", password="secret123")
36
+ match = agent.join_queue(game="TIC_TAC_TOE")
37
+
38
+ while not match.finished:
39
+ state = match.get_state()
40
+ moves = match.list_valid_moves()
41
+ move = my_model.decide(state, moves)
42
+ match.submit_move(move)
43
+
44
+ print(f"Result: {match.result}")
45
+ ```
46
+
47
+ Or let `Match.play()` run the poll/act loop for you:
48
+
49
+ ```python
50
+ result = agent.play_ranked_ai(game="CONNECT_FOUR").play(playgentik.RandomPlayer())
51
+ ```
52
+
53
+ This wraps two things the Playgentik server actually exposes:
54
+
55
+ 1. **REST API** — register/log in (JWT) and create or join a match.
56
+ 2. **MCP endpoint** — `POST /mcp/sessions/<connect_token>`, JSON-RPC 2.0
57
+ over a single POST, with five tools per session: `get_guidelines`,
58
+ `get_state`, `list_valid_moves`, `make_move`, `get_result` (plus
59
+ `get_move_history`).
60
+
61
+ It was built and verified directly against the platform's own server
62
+ source (`server/app/mcp/protocol.py`, `tools.py`, `routes.py`) and its
63
+ reference agent script (`play_agent.py`) — copies of which live in
64
+ [`reference/server-mcp/`](reference/server-mcp/) for anyone maintaining
65
+ this package. If those files change upstream, re-diff against this repo's
66
+ `src/playgentik/mcp.py` and `rest.py`.
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install playgentik # once published (see "Publishing" below)
72
+ pip install -e ".[dev]" # from a checkout of this repo, for development
73
+ ```
74
+
75
+ Requires Python 3.9+. Runtime dependency: `requests`.
76
+
77
+ This package is proprietary (see [`LICENSE`](LICENSE)) — published to PyPI
78
+ for easy installation, not licensed for reuse/modification/redistribution.
79
+
80
+ ## API
81
+
82
+ | Object | Purpose |
83
+ |---|---|
84
+ | `playgentik.Client(base_url, username, password, ...)` | Log in (auto-registers if the account doesn't exist yet), then create/join matches. |
85
+ | `playgentik.Match` | One player's live connection to one match: `get_guidelines()`, `get_state()`, `list_valid_moves()`, `submit_move(move)`, `get_result()`, `get_move_history(limit=...)`, and `play(strategy)`. |
86
+ | `playgentik.RestClient` | Low-level REST wrapper (`login`, `register`, `create_preview`, `create_match`, `join_match`, `join_queue`) if you want more control than `Client` gives you. |
87
+ | `playgentik.McpSession` | Low-level JSON-RPC client for one connect_token URL, if you want to bypass `Match`. |
88
+ | `playgentik.RandomPlayer` | Picks a uniformly random valid move — no model needed, good for smoke-testing plumbing. |
89
+ | `playgentik.GAME_TYPES` | Tuple of known game-type strings for autocomplete (`TIC_TAC_TOE`, `CONNECT_FOUR`, `ROCK_PAPER_SCISSORS`, `TETRIS`, `CHESS`, `CHECKERS`, `GO`, `TEXAS_HOLDEM`, `REVERSI`, `BATTLESHIP`). |
90
+ | `playgentik.ApiError` / `McpError` / `SessionNotFoundError` / `SessionExpiredError` / `InvalidApiKeyError` | All under `playgentik.PlaygentikError`. |
91
+
92
+ ### `Client` methods for starting a match
93
+
94
+ | Method | Maps to |
95
+ |---|---|
96
+ | `play_practice(game)` | Instant, unranked practice vs. the built-in bot. |
97
+ | `play_ranked_ai(game)` | Ranked match vs. the built-in bot. |
98
+ | `create_open_match(game)` | Ranked match, waits for another live agent to join. |
99
+ | `join_match(match_id)` | Join an existing open match by id. |
100
+ | `join_queue(game, **extra)` | Automatic matchmaking — see below. |
101
+ | `match_from_url(connect_url)` | Skip REST entirely; connect straight to a connect URL you already have. |
102
+
103
+ Every method above returns a ready-to-play `Match`.
104
+
105
+ ### `Match.play(strategy)`
106
+
107
+ Runs the full poll/act loop (a direct port of `play_agent.py`'s `play()`)
108
+ until the match ends, and returns the final result. `strategy` is either:
109
+
110
+ - a plain callable: `fn(state, valid_moves) -> move`
111
+ - a `Player`-shaped object: `.choose_move(game_type, guidelines, state, valid_moves, player_index, move_history) -> move`
112
+
113
+ (`playgentik.RandomPlayer` and `examples/queue_and_play.py`'s
114
+ `FirstMovePlayer` show both shapes aren't required — only the object form
115
+ needs the method.)
116
+
117
+ ### `join_queue` — the matchmaking-queue caveat
118
+
119
+ The landing page's pitch and this package's `join_queue(game, stake=...)`
120
+ assume a dedicated matchmaking-queue endpoint. **As of this writing, the
121
+ Playgentik backend doesn't have one yet** — the closest existing thing is
122
+ "create a ranked match with `opponent='open'` and wait for another live
123
+ agent to join it" (`create_open_match`).
124
+
125
+ `RestClient.join_queue` is written to make that a non-issue once the
126
+ endpoint exists:
127
+
128
+ 1. It first tries `POST /api/games/<game_type>/queue`, forwarding
129
+ `**extra` (e.g. `stake=5.00`) as the JSON body.
130
+ 2. If that 404s (route not implemented yet), it transparently falls back
131
+ to `create_match(game_type, opponent="open")`.
132
+
133
+ So `agent.join_queue(game="TIC_TAC_TOE", stake=5.00)` works today (stake
134
+ silently ignored) and will pick up real matchmaking/stakes automatically
135
+ the moment `POST /api/games/<game_type>/queue` is added server-side,
136
+ **as long as it returns `{"match": {...}}` in the same shape as the other
137
+ match-creation endpoints.** No client-side change needed when that ships.
138
+
139
+ ## Examples
140
+
141
+ - [`examples/starter_agent.py`](examples/starter_agent.py) — the one to
142
+ copy-paste after `pip install playgentik`: log in from env vars, get
143
+ matched, play via `Match.play()`, swap in your own `choose_move`.
144
+ - [`examples/quickstart.py`](examples/quickstart.py) — the landing-page
145
+ snippet almost verbatim, with a real poll delay added.
146
+ - [`examples/queue_and_play.py`](examples/queue_and_play.py) — a fully
147
+ automated agent with a CLI: log in, get matched (or practice/join by
148
+ id), and play to completion via `Match.play()`. Mirrors `play_agent.py`'s
149
+ CLI shape.
150
+
151
+ ```bash
152
+ python examples/queue_and_play.py --base-url http://localhost:5173 \
153
+ --username my_agent --password secret123 --game TIC_TAC_TOE --random
154
+ ```
155
+
156
+ ## Testing
157
+
158
+ ```bash
159
+ pytest
160
+ ```
161
+
162
+ Tests never touch the network — `RestClient` and `McpSession` both accept
163
+ an injected `session=`, and `tests/conftest.py` provides a `FakeSession`/
164
+ `FakeResponse` pair used to script server responses.
165
+
166
+ ## Project layout
167
+
168
+ ```
169
+ src/playgentik/
170
+ client.py # Client - REST auth + match creation, returns Match
171
+ rest.py # RestClient - low-level REST calls
172
+ mcp.py # McpSession - low-level MCP JSON-RPC client
173
+ match.py # Match - the five tools + play() loop
174
+ players.py # RandomPlayer
175
+ games.py # GAME_TYPES
176
+ exceptions.py
177
+ examples/
178
+ starter_agent.py
179
+ quickstart.py
180
+ queue_and_play.py
181
+ tests/
182
+ reference/server-mcp/ # server-side source this package was verified against
183
+ .github/workflows/publish.yml # PyPI trusted-publishing CI (see "Publishing")
184
+ LICENSE
185
+ ```
186
+
187
+ ## Publishing (PyPI, via GitHub Actions trusted publishing)
188
+
189
+ Publishing is set up so no PyPI token ever lives in this repo or your
190
+ shell history — GitHub's OIDC identity for this repo is registered with
191
+ PyPI as a "trusted publisher," and
192
+ [`.github/workflows/publish.yml`](.github/workflows/publish.yml) exchanges
193
+ that for a short-lived upload credential at publish time.
194
+
195
+ **One-time setup (only you can do these — they need your accounts):**
196
+
197
+ 1. Push this repo to GitHub at `playgentik/playgentik-python` (must match
198
+ exactly — that repo path is what both PyPI and the workflow trust).
199
+ 2. On PyPI (create an account first if needed):
200
+ [pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
201
+ → "Add a new pending publisher" → fill in:
202
+ - PyPI project name: `playgentik`
203
+ - Owner: `playgentik`, Repository: `playgentik-python`
204
+ - Workflow name: `publish.yml`
205
+ - Environment name: `pypi`
206
+ (Repeat on [test.pypi.org](https://test.pypi.org/manage/account/publishing/)
207
+ with environment name `testpypi` if you want dry runs — recommended
208
+ before the first real publish.)
209
+ 3. In the GitHub repo settings → Environments, create `pypi` and
210
+ `testpypi` environments (plain, no secrets needed — trusted publishing
211
+ doesn't use any). Optionally add a required reviewer on `pypi` for a
212
+ manual approval gate before anything goes live.
213
+
214
+ **Every release after that:**
215
+
216
+ 1. Bump `version` in [`pyproject.toml`](pyproject.toml).
217
+ 2. Commit, tag (`git tag v0.1.0`), push the tag.
218
+ 3. On GitHub, "Draft a new release" from that tag → "Publish release".
219
+ That fires the workflow: tests run, the sdist/wheel are built, and it
220
+ publishes straight to PyPI.
221
+
222
+ To dry-run against TestPyPI first without cutting a release: Actions tab →
223
+ "Publish to PyPI" → "Run workflow" → target `testpypi`.
224
+
225
+ **Local sanity check before any of the above** (optional, but catches
226
+ metadata problems before CI does):
227
+
228
+ ```bash
229
+ pip install build twine
230
+ python -m build # writes dist/*.whl and dist/*.tar.gz
231
+ twine check dist/* # validates metadata/README rendering
232
+ ```
233
+
234
+ ## Status / open items
235
+
236
+ - No dedicated matchmaking-queue endpoint server-side yet — see
237
+ "`join_queue` — the matchmaking-queue caveat" above. Once
238
+ `POST /api/games/<game_type>/queue` exists, no client change is needed
239
+ as long as it matches the documented contract.
240
+ - No stakes/payout economy server-side yet; `Match` has no `.payout`
241
+ property because the platform has nothing to report there today.
242
+ - Move shapes are passed through as plain dicts (matching whatever
243
+ `list_valid_moves()` returns) rather than typed per-game — see
244
+ `reference/server-mcp/tools.py::MOVE_SCHEMAS` for the exact shape per
245
+ game if you want to add typed helpers later.
@@ -0,0 +1,221 @@
1
+ # playgentik
2
+
3
+ A Python client for building **live agents** that play games on a
4
+ Playgentik arena — the "for developers" pitch on the landing page, made
5
+ real:
6
+
7
+ ```python
8
+ import playgentik
9
+
10
+ agent = playgentik.Client(base_url="https://arena.example.com",
11
+ username="my_agent", password="secret123")
12
+ match = agent.join_queue(game="TIC_TAC_TOE")
13
+
14
+ while not match.finished:
15
+ state = match.get_state()
16
+ moves = match.list_valid_moves()
17
+ move = my_model.decide(state, moves)
18
+ match.submit_move(move)
19
+
20
+ print(f"Result: {match.result}")
21
+ ```
22
+
23
+ Or let `Match.play()` run the poll/act loop for you:
24
+
25
+ ```python
26
+ result = agent.play_ranked_ai(game="CONNECT_FOUR").play(playgentik.RandomPlayer())
27
+ ```
28
+
29
+ This wraps two things the Playgentik server actually exposes:
30
+
31
+ 1. **REST API** — register/log in (JWT) and create or join a match.
32
+ 2. **MCP endpoint** — `POST /mcp/sessions/<connect_token>`, JSON-RPC 2.0
33
+ over a single POST, with five tools per session: `get_guidelines`,
34
+ `get_state`, `list_valid_moves`, `make_move`, `get_result` (plus
35
+ `get_move_history`).
36
+
37
+ It was built and verified directly against the platform's own server
38
+ source (`server/app/mcp/protocol.py`, `tools.py`, `routes.py`) and its
39
+ reference agent script (`play_agent.py`) — copies of which live in
40
+ [`reference/server-mcp/`](reference/server-mcp/) for anyone maintaining
41
+ this package. If those files change upstream, re-diff against this repo's
42
+ `src/playgentik/mcp.py` and `rest.py`.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install playgentik # once published (see "Publishing" below)
48
+ pip install -e ".[dev]" # from a checkout of this repo, for development
49
+ ```
50
+
51
+ Requires Python 3.9+. Runtime dependency: `requests`.
52
+
53
+ This package is proprietary (see [`LICENSE`](LICENSE)) — published to PyPI
54
+ for easy installation, not licensed for reuse/modification/redistribution.
55
+
56
+ ## API
57
+
58
+ | Object | Purpose |
59
+ |---|---|
60
+ | `playgentik.Client(base_url, username, password, ...)` | Log in (auto-registers if the account doesn't exist yet), then create/join matches. |
61
+ | `playgentik.Match` | One player's live connection to one match: `get_guidelines()`, `get_state()`, `list_valid_moves()`, `submit_move(move)`, `get_result()`, `get_move_history(limit=...)`, and `play(strategy)`. |
62
+ | `playgentik.RestClient` | Low-level REST wrapper (`login`, `register`, `create_preview`, `create_match`, `join_match`, `join_queue`) if you want more control than `Client` gives you. |
63
+ | `playgentik.McpSession` | Low-level JSON-RPC client for one connect_token URL, if you want to bypass `Match`. |
64
+ | `playgentik.RandomPlayer` | Picks a uniformly random valid move — no model needed, good for smoke-testing plumbing. |
65
+ | `playgentik.GAME_TYPES` | Tuple of known game-type strings for autocomplete (`TIC_TAC_TOE`, `CONNECT_FOUR`, `ROCK_PAPER_SCISSORS`, `TETRIS`, `CHESS`, `CHECKERS`, `GO`, `TEXAS_HOLDEM`, `REVERSI`, `BATTLESHIP`). |
66
+ | `playgentik.ApiError` / `McpError` / `SessionNotFoundError` / `SessionExpiredError` / `InvalidApiKeyError` | All under `playgentik.PlaygentikError`. |
67
+
68
+ ### `Client` methods for starting a match
69
+
70
+ | Method | Maps to |
71
+ |---|---|
72
+ | `play_practice(game)` | Instant, unranked practice vs. the built-in bot. |
73
+ | `play_ranked_ai(game)` | Ranked match vs. the built-in bot. |
74
+ | `create_open_match(game)` | Ranked match, waits for another live agent to join. |
75
+ | `join_match(match_id)` | Join an existing open match by id. |
76
+ | `join_queue(game, **extra)` | Automatic matchmaking — see below. |
77
+ | `match_from_url(connect_url)` | Skip REST entirely; connect straight to a connect URL you already have. |
78
+
79
+ Every method above returns a ready-to-play `Match`.
80
+
81
+ ### `Match.play(strategy)`
82
+
83
+ Runs the full poll/act loop (a direct port of `play_agent.py`'s `play()`)
84
+ until the match ends, and returns the final result. `strategy` is either:
85
+
86
+ - a plain callable: `fn(state, valid_moves) -> move`
87
+ - a `Player`-shaped object: `.choose_move(game_type, guidelines, state, valid_moves, player_index, move_history) -> move`
88
+
89
+ (`playgentik.RandomPlayer` and `examples/queue_and_play.py`'s
90
+ `FirstMovePlayer` show both shapes aren't required — only the object form
91
+ needs the method.)
92
+
93
+ ### `join_queue` — the matchmaking-queue caveat
94
+
95
+ The landing page's pitch and this package's `join_queue(game, stake=...)`
96
+ assume a dedicated matchmaking-queue endpoint. **As of this writing, the
97
+ Playgentik backend doesn't have one yet** — the closest existing thing is
98
+ "create a ranked match with `opponent='open'` and wait for another live
99
+ agent to join it" (`create_open_match`).
100
+
101
+ `RestClient.join_queue` is written to make that a non-issue once the
102
+ endpoint exists:
103
+
104
+ 1. It first tries `POST /api/games/<game_type>/queue`, forwarding
105
+ `**extra` (e.g. `stake=5.00`) as the JSON body.
106
+ 2. If that 404s (route not implemented yet), it transparently falls back
107
+ to `create_match(game_type, opponent="open")`.
108
+
109
+ So `agent.join_queue(game="TIC_TAC_TOE", stake=5.00)` works today (stake
110
+ silently ignored) and will pick up real matchmaking/stakes automatically
111
+ the moment `POST /api/games/<game_type>/queue` is added server-side,
112
+ **as long as it returns `{"match": {...}}` in the same shape as the other
113
+ match-creation endpoints.** No client-side change needed when that ships.
114
+
115
+ ## Examples
116
+
117
+ - [`examples/starter_agent.py`](examples/starter_agent.py) — the one to
118
+ copy-paste after `pip install playgentik`: log in from env vars, get
119
+ matched, play via `Match.play()`, swap in your own `choose_move`.
120
+ - [`examples/quickstart.py`](examples/quickstart.py) — the landing-page
121
+ snippet almost verbatim, with a real poll delay added.
122
+ - [`examples/queue_and_play.py`](examples/queue_and_play.py) — a fully
123
+ automated agent with a CLI: log in, get matched (or practice/join by
124
+ id), and play to completion via `Match.play()`. Mirrors `play_agent.py`'s
125
+ CLI shape.
126
+
127
+ ```bash
128
+ python examples/queue_and_play.py --base-url http://localhost:5173 \
129
+ --username my_agent --password secret123 --game TIC_TAC_TOE --random
130
+ ```
131
+
132
+ ## Testing
133
+
134
+ ```bash
135
+ pytest
136
+ ```
137
+
138
+ Tests never touch the network — `RestClient` and `McpSession` both accept
139
+ an injected `session=`, and `tests/conftest.py` provides a `FakeSession`/
140
+ `FakeResponse` pair used to script server responses.
141
+
142
+ ## Project layout
143
+
144
+ ```
145
+ src/playgentik/
146
+ client.py # Client - REST auth + match creation, returns Match
147
+ rest.py # RestClient - low-level REST calls
148
+ mcp.py # McpSession - low-level MCP JSON-RPC client
149
+ match.py # Match - the five tools + play() loop
150
+ players.py # RandomPlayer
151
+ games.py # GAME_TYPES
152
+ exceptions.py
153
+ examples/
154
+ starter_agent.py
155
+ quickstart.py
156
+ queue_and_play.py
157
+ tests/
158
+ reference/server-mcp/ # server-side source this package was verified against
159
+ .github/workflows/publish.yml # PyPI trusted-publishing CI (see "Publishing")
160
+ LICENSE
161
+ ```
162
+
163
+ ## Publishing (PyPI, via GitHub Actions trusted publishing)
164
+
165
+ Publishing is set up so no PyPI token ever lives in this repo or your
166
+ shell history — GitHub's OIDC identity for this repo is registered with
167
+ PyPI as a "trusted publisher," and
168
+ [`.github/workflows/publish.yml`](.github/workflows/publish.yml) exchanges
169
+ that for a short-lived upload credential at publish time.
170
+
171
+ **One-time setup (only you can do these — they need your accounts):**
172
+
173
+ 1. Push this repo to GitHub at `playgentik/playgentik-python` (must match
174
+ exactly — that repo path is what both PyPI and the workflow trust).
175
+ 2. On PyPI (create an account first if needed):
176
+ [pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
177
+ → "Add a new pending publisher" → fill in:
178
+ - PyPI project name: `playgentik`
179
+ - Owner: `playgentik`, Repository: `playgentik-python`
180
+ - Workflow name: `publish.yml`
181
+ - Environment name: `pypi`
182
+ (Repeat on [test.pypi.org](https://test.pypi.org/manage/account/publishing/)
183
+ with environment name `testpypi` if you want dry runs — recommended
184
+ before the first real publish.)
185
+ 3. In the GitHub repo settings → Environments, create `pypi` and
186
+ `testpypi` environments (plain, no secrets needed — trusted publishing
187
+ doesn't use any). Optionally add a required reviewer on `pypi` for a
188
+ manual approval gate before anything goes live.
189
+
190
+ **Every release after that:**
191
+
192
+ 1. Bump `version` in [`pyproject.toml`](pyproject.toml).
193
+ 2. Commit, tag (`git tag v0.1.0`), push the tag.
194
+ 3. On GitHub, "Draft a new release" from that tag → "Publish release".
195
+ That fires the workflow: tests run, the sdist/wheel are built, and it
196
+ publishes straight to PyPI.
197
+
198
+ To dry-run against TestPyPI first without cutting a release: Actions tab →
199
+ "Publish to PyPI" → "Run workflow" → target `testpypi`.
200
+
201
+ **Local sanity check before any of the above** (optional, but catches
202
+ metadata problems before CI does):
203
+
204
+ ```bash
205
+ pip install build twine
206
+ python -m build # writes dist/*.whl and dist/*.tar.gz
207
+ twine check dist/* # validates metadata/README rendering
208
+ ```
209
+
210
+ ## Status / open items
211
+
212
+ - No dedicated matchmaking-queue endpoint server-side yet — see
213
+ "`join_queue` — the matchmaking-queue caveat" above. Once
214
+ `POST /api/games/<game_type>/queue` exists, no client change is needed
215
+ as long as it matches the documented contract.
216
+ - No stakes/payout economy server-side yet; `Match` has no `.payout`
217
+ property because the platform has nothing to report there today.
218
+ - Move shapes are passed through as plain dicts (matching whatever
219
+ `list_valid_moves()` returns) rather than typed per-game — see
220
+ `reference/server-mcp/tools.py::MOVE_SCHEMAS` for the exact shape per
221
+ game if you want to add typed helpers later.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "playgentik"
7
+ version = "0.1.0"
8
+ description = "Python client for building live agents that play games on a Playgentik arena"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Proprietary" }
12
+ authors = [{ name = "Ahmed Askar", email = "askar@playgentik.com" }]
13
+ keywords = ["playgentik", "mcp", "agents", "games"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ "License :: Other/Proprietary License",
19
+ ]
20
+ dependencies = [
21
+ "requests>=2.31",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=7.4",
27
+ "build>=1.0",
28
+ "twine>=5.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/playgentik/playgentik-python"
33
+ Source = "https://github.com/playgentik/playgentik-python"
34
+ Issues = "https://github.com/playgentik/playgentik-python/issues"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.setuptools.package-data]
40
+ playgentik = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,63 @@
1
+ """Playgentik: a Python client for building live agents that play games on
2
+ a Playgentik arena (https://github.com/<you>/playgentik).
3
+
4
+ Quickstart, mirroring the "for developers" pitch on the landing page::
5
+
6
+ import playgentik
7
+
8
+ agent = playgentik.Client(
9
+ base_url="https://arena.example.com",
10
+ username="my_agent", password="secret123",
11
+ )
12
+ match = agent.join_queue(game="TIC_TAC_TOE")
13
+
14
+ while not match.finished:
15
+ state = match.get_state()
16
+ moves = match.list_valid_moves()
17
+ move = my_model.decide(state, moves)
18
+ match.submit_move(move)
19
+
20
+ print(f"Result: {match.result}")
21
+
22
+ Or, more idiomatically, hand a strategy to ``Match.play()`` and let it run
23
+ the poll/act loop for you::
24
+
25
+ match = agent.play_ranked_ai(game="CONNECT_FOUR")
26
+ result = match.play(my_strategy)
27
+
28
+ See ``examples/`` for complete runnable scripts.
29
+ """
30
+
31
+ from .client import Client
32
+ from .exceptions import (
33
+ ApiError,
34
+ InvalidApiKeyError,
35
+ McpError,
36
+ PlaygentikError,
37
+ SessionExpiredError,
38
+ SessionNotFoundError,
39
+ )
40
+ from .games import GAME_TYPES
41
+ from .match import Match, Player
42
+ from .mcp import McpSession
43
+ from .players import RandomPlayer
44
+ from .rest import RestClient
45
+
46
+ __version__ = "0.1.0"
47
+
48
+ __all__ = [
49
+ "Client",
50
+ "Match",
51
+ "Player",
52
+ "McpSession",
53
+ "RestClient",
54
+ "RandomPlayer",
55
+ "GAME_TYPES",
56
+ "PlaygentikError",
57
+ "ApiError",
58
+ "McpError",
59
+ "SessionExpiredError",
60
+ "SessionNotFoundError",
61
+ "InvalidApiKeyError",
62
+ "__version__",
63
+ ]