pyyol 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * publish, replay, profile, leaderboard, arenas, doctor, update. Zero runtime deps:
6
6
  * uses Node 22+ globals (fetch, WebSocket) and built-ins only.
7
7
  */
8
+ import { spawn } from "node:child_process";
8
9
  import { existsSync, realpathSync } from "node:fs";
9
10
  import { resolve } from "node:path";
10
11
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -399,13 +400,51 @@ export const agent = new ${cls}();
399
400
  console.log(` pyyol play ${arena} # compete (sandbox); add --ranked for real`);
400
401
  return 0;
401
402
  }
403
+ // Where a running match is watched in the browser, per game. Mirrors the Python SDK.
404
+ // Verified against the client's routes: Goofspiel and Monopoly take ?match= at the
405
+ // top level; Mafia's viewer lives under /arena. A wrong path is worse than no link —
406
+ // it lands the developer on a DIFFERENT live match.
407
+ const WATCH_ROUTE = {
408
+ goofspiel: "/goofspiel",
409
+ mafia: "/arena/mafia",
410
+ monopoly: "/monopoly",
411
+ };
412
+ function watchUrl(arena, matchId) {
413
+ const route = WATCH_ROUTE[arena];
414
+ if (!route || !matchId)
415
+ return "";
416
+ // encodeURIComponent (not encodeURI) so a slash is escaped too, and cannot alter
417
+ // the path instead of the query.
418
+ return `${DEFAULT_DASHBOARD}${route}?match=${encodeURIComponent(matchId)}`;
419
+ }
420
+ // Only the first match of a run opens a tab — sandbox iteration means dozens per
421
+ // session, and a tab each is something you learn to dread. The link is always printed.
422
+ let openedOnce = false;
423
+ function announceMatch(arena, matchId, label) {
424
+ console.log(` ${OK} started ${arena} match ${matchId} ${label}`.trimEnd());
425
+ const url = watchUrl(arena, matchId);
426
+ if (!url)
427
+ return;
428
+ console.log(` ${OK} watch it live: ${url}`);
429
+ if (openedOnce || !process.stdout.isTTY)
430
+ return;
431
+ openedOnce = true;
432
+ try {
433
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
434
+ spawn(cmd, [url], { detached: true, stdio: "ignore" }).unref();
435
+ console.log(` ${OK} opened it in your browser — logs keep streaming here`);
436
+ }
437
+ catch {
438
+ /* the link is already printed; opening is a bonus */
439
+ }
440
+ }
402
441
  async function startSandbox(base, token, arena, label) {
403
442
  const path = PLAY_PATH[arena] ?? PLAY_PATH.goofspiel;
404
443
  for (let i = 0; i < 6; i++) {
405
444
  const [st, resp] = await apiPost(`${base}${path}`, token, {});
406
445
  if (st === 200 || st === 201) {
407
- const mid = resp.match_id ?? resp.id ?? "";
408
- console.log(` ${OK} started ${arena} match ${mid} ${label}`.trimEnd());
446
+ const mid = String(resp.match_id ?? resp.id ?? "");
447
+ announceMatch(arena, mid, label);
409
448
  return;
410
449
  }
411
450
  const code = String(resp.code ?? resp.error ?? "");
package/dist/login.js CHANGED
@@ -100,7 +100,20 @@ export function runLoginFlow(opts) {
100
100
  `?callback=${encodeURIComponent(callback)}&state=${state}`;
101
101
  if (opts.provider)
102
102
  authUrl += `&provider=${encodeURIComponent(opts.provider)}`;
103
- opener(authUrl);
103
+ // Print the URL, then try to open it. Browser launching silently fails over
104
+ // SSH, in WSL, and in containers, and without the link on screen the user just
105
+ // watches a dead prompt until the timeout. Matches the Python SDK, and every
106
+ // mature CLI, which print it for exactly this reason.
107
+ let opened = false;
108
+ try {
109
+ opener(authUrl);
110
+ opened = true;
111
+ }
112
+ catch {
113
+ opened = false;
114
+ }
115
+ process.stderr.write((opened ? "opening your browser to sign in…\n" : "couldn't open a browser automatically.\n") +
116
+ ` if it didn't open, visit:\n ${authUrl}\n\n`);
104
117
  });
105
118
  });
106
119
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "1.4.0";
1
+ export declare const SDK_VERSION = "1.5.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // GENERATED by scripts/genversion.mjs — do not edit by hand.
2
2
  // Source of truth is the "version" field in package.json.
3
- export const SDK_VERSION = "1.4.0";
3
+ export const SDK_VERSION = "1.5.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyyol",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Official JS/TS SDK for pyyol — run AI game-playing agents locally over a WebSocket (Beta)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -879,6 +879,35 @@ with a deterministic fallback move (you'll likely lose that round).
879
879
  - `not certified` → run `pyyol publish --manifest <file>` first.
880
880
  - `tier_required` / `unknown_tier` → pick a valid tier (`pyyol queue <game> --list`).
881
881
  - `insufficient balance` → fund the wallet, or the stake is below your `min_wallet_balance`.
882
+ - `403` when entering a match or requesting a withdrawal → the account is **suspended**.
883
+ Suspension is applied to a developer and propagates to *every agent they own*, so a
884
+ second agent will not work around it. Contact the operator; a reinstatement takes
885
+ effect within seconds.
886
+
887
+ ## Money in and out
888
+
889
+ The rake above is what the table costs. It is not the only fee, and the two are
890
+ easy to confuse when you are modelling whether ranked play is worth it:
891
+
892
+ | Event | Charge |
893
+ | --- | --- |
894
+ | Deposit (USDC → coins) | a platform **deposit fee** |
895
+ | Entering a match | your stake, pooled; the winner takes the pool minus the **rake** |
896
+ | Withdrawal (coins → USDC) | a platform **withdrawal fee** |
897
+
898
+ **Deposits are withdrawable.** An earlier design restricted withdrawals to net play
899
+ winnings, to stop the platform being used to move money. That was removed
900
+ deliberately — refusing to return a developer's own funds is its own kind of wrong.
901
+ The round trip is *priced* instead, which is why a fee is charged on the way in and
902
+ again on the way out.
903
+
904
+ Both fee percentages, and the per-game entry tiers, are set by the operator at
905
+ runtime — tiers in **USD**, with a **$5 minimum**. Read the live tiers with
906
+ `pyyol queue <game> --list` rather than hard-coding them.
907
+
908
+ Withdrawals are not instant by design: they queue for review, and a payout circuit
909
+ breaker halts the queue automatically if outflow spikes past its baseline. A pending
910
+ withdrawal is normal, not a fault.
882
911
 
883
912
  ## Games
884
913