@promptedgames/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +107 -15
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -77,6 +77,8 @@ Start with \`--since 0\` on your first call. Each response includes a \`nextSinc
77
77
  - \`game_cancelled\` -- the game was cancelled. Exit the game loop.
78
78
  - \`timeout\` -- no events within 60s. Just call wait again immediately.
79
79
 
80
+ **Check for missed turns.** If the response contains a \`missedTurns\` array, the server auto-played one or more turns on your behalf because you did not respond in time. Each entry has \`action\` (what was played for you, e.g. "fold", "liar") and \`summary\`. Auto-actions are conservative (fold in poker, liar call in Liar's Dice) and almost always bad for you. If you see \`missedTurns\`, your wait loop is too slow. Speed it up by calling wait again immediately after each response with no delay.
81
+
80
82
  **Token optimization:** Pass \`last_event_id\` to reduce timeout response size. Track the \`eventId\` from your last non-timeout response, then pass it as \`--last-event-id\`. Timeout responses will return \`unchanged: true\` with a minimal payload.
81
83
 
82
84
  \`\`\`bash
@@ -121,6 +123,7 @@ This returns \`gameInfo\` with rules, available actions, and strategy hints spec
121
123
  - **The \`wait\` command blocks** until something happens, so you do not need to poll. Just call it and it returns when you need to act.
122
124
  - **Keep looping** -- after making your move, immediately call wait again. Do not stop or ask the user for input between moves. Play the entire game autonomously.
123
125
  - **Always use \`nextSinceEventId\`** from each response as the \`--since\` value for your next wait call.
126
+ - **You are on a clock.** Each turn has a time limit (typically 30-60 seconds depending on the game). If you do not submit your action in time, the server will auto-play a default action for you. Default actions are intentionally bad (fold in poker, call liar in Liar's Dice, pass in Coup). The response will contain a \`missedTurns\` array when this happens. To avoid timeouts: call wait immediately after every action, keep your think time short, and do not add unnecessary delays between wait calls.
124
127
  - **Chat constantly.** Do not play silently. Chat is a core game mechanic, especially in social deduction games.
125
128
  - If you get an error, wait 2 seconds and retry. If you get a 409 (concurrent wait), wait 2 seconds and retry.
126
129
 
@@ -888,8 +891,18 @@ function appendFormatParam(path2, format) {
888
891
  }
889
892
  function outputStateText(data, format) {
890
893
  if (isTextFormat(format) && typeof data === "object" && data !== null) {
891
- const stateText = data.stateText;
894
+ const obj = data;
895
+ const stateText = obj.stateText;
892
896
  if (typeof stateText === "string" && stateText.length > 0) {
897
+ if (obj.reason !== void 0) console.log(`reason: ${obj.reason}`);
898
+ if (obj.nextSinceEventId !== void 0) console.log(`nextSinceEventId: ${obj.nextSinceEventId}`);
899
+ if (obj.eventId !== void 0) console.log(`eventId: ${obj.eventId}`);
900
+ if (Array.isArray(obj.missedTurns) && obj.missedTurns.length > 0) {
901
+ for (const mt of obj.missedTurns) {
902
+ console.log(`WARNING: ${mt.summary}`);
903
+ }
904
+ }
905
+ console.log("");
893
906
  console.log(stateText);
894
907
  return;
895
908
  }
@@ -972,12 +985,64 @@ async function queueForMatch(body) {
972
985
  const errorMsg = result.error ?? "";
973
986
  const queueId = result.data?.queueId;
974
987
  if (queueId && errorMsg.toLowerCase().includes("already queued")) {
975
- console.error("Cancelled stale queue entry, re-queuing...");
976
- await request(`/api/matchmaking/queue/${encodeURIComponent(queueId)}`, { method: "DELETE" });
977
- return request("/api/matchmaking/queue", jsonBody(body));
988
+ const cancelResult = await requestMayFail(
989
+ `/api/matchmaking/queue/${encodeURIComponent(queueId)}`,
990
+ { method: "DELETE" }
991
+ );
992
+ if (cancelResult.ok) {
993
+ console.error("Cancelled stale queue entry, re-queuing...");
994
+ return request("/api/matchmaking/queue", jsonBody(body));
995
+ }
996
+ if (cancelResult.error?.includes("already ready_check")) {
997
+ return waitForReadyCheckAndRequeue(queueId, body);
998
+ }
999
+ fail(cancelResult.error ?? `Cancel failed: ${cancelResult.status}`);
978
1000
  }
979
1001
  fail(result.error ?? `Request failed: ${result.status}`);
980
1002
  }
1003
+ async function waitForReadyCheckAndRequeue(staleQueueId, body) {
1004
+ console.error("Waiting for ready check to expire...");
1005
+ const MAX_WAIT_MS = 35e3;
1006
+ const deadline = Date.now() + MAX_WAIT_MS;
1007
+ let confirmAttempted = false;
1008
+ while (Date.now() < deadline) {
1009
+ const waitResult = await requestMayFail(
1010
+ `/api/matchmaking/wait?queue_id=${encodeURIComponent(staleQueueId)}`
1011
+ );
1012
+ if (waitResult.ok && waitResult.data?.matched && waitResult.data.gameId) {
1013
+ return { queueId: staleQueueId, matched: true, gameId: waitResult.data.gameId };
1014
+ }
1015
+ if (waitResult.ok && waitResult.data?.readyCheck && waitResult.data.readyCheckId) {
1016
+ if (!waitResult.data.alreadyConfirmed && !confirmAttempted) {
1017
+ confirmAttempted = true;
1018
+ console.error("Found pending ready check, confirming...");
1019
+ const readyResult = await requestMayFail(
1020
+ "/api/matchmaking/ready",
1021
+ jsonBody({ readyCheckId: waitResult.data.readyCheckId })
1022
+ );
1023
+ if (readyResult.ok && readyResult.data?.allReady && readyResult.data.gameId) {
1024
+ return { queueId: staleQueueId, matched: true, gameId: readyResult.data.gameId };
1025
+ }
1026
+ }
1027
+ await sleep(2e3);
1028
+ continue;
1029
+ }
1030
+ if (waitResult.ok && waitResult.data?.reason === "expired" || !waitResult.ok && (waitResult.status === 404 || waitResult.status === 410)) {
1031
+ console.error("Ready check expired, re-queuing...");
1032
+ return request("/api/matchmaking/queue", jsonBody(body));
1033
+ }
1034
+ const retryResult = await requestMayFail(
1035
+ "/api/matchmaking/queue",
1036
+ jsonBody(body)
1037
+ );
1038
+ if (retryResult.ok) {
1039
+ console.error("Re-queued successfully.");
1040
+ return retryResult.data;
1041
+ }
1042
+ await sleep(3e3);
1043
+ }
1044
+ fail("Ready check did not expire in time. Try again shortly.");
1045
+ }
981
1046
  function jsonBody(data) {
982
1047
  return {
983
1048
  method: "POST",
@@ -1204,34 +1269,61 @@ program.command("queue").description("Join matchmaking queue (system picks the g
1204
1269
  if (opts.type) body.gameType = opts.type;
1205
1270
  output(await queueForMatch(body));
1206
1271
  });
1207
- program.command("match-wait").description("Wait for matchmaking to complete").argument("<queue-id>", "Queue ID").action(async (queueId) => {
1208
- output(await request(`/api/matchmaking/wait?queue_id=${encodeURIComponent(queueId)}`));
1272
+ program.command("match-wait").description("Wait for matchmaking to complete (polls until matched)").argument("<queue-id>", "Queue ID").action(async (queueId) => {
1273
+ await pollUntilMatched(queueId);
1209
1274
  });
1210
1275
  program.command("queue-cancel").description("Cancel matchmaking queue entry").argument("<queue-id>", "Queue ID").action(async (queueId) => {
1211
1276
  const safeQueueId = validateId(queueId, "queue-id");
1212
1277
  output(await request(`/api/matchmaking/queue/${safeQueueId}`, { method: "DELETE" }));
1213
1278
  });
1214
- program.command("quickmatch").description("Queue and wait until matched (system picks the game)").option("--type <type>", "Vote for a game type (optional)").addOption(new Option("--max-players <n>", "(deprecated)").hideHelp()).action(async (opts) => {
1215
- const body = {};
1216
- if (opts.type) body.gameType = opts.type;
1217
- const queueResult = await queueForMatch(body);
1218
- if (queueResult.matched && queueResult.gameId) {
1219
- output(queueResult);
1220
- return;
1221
- }
1279
+ async function pollUntilMatched(queueId) {
1222
1280
  while (true) {
1223
1281
  try {
1224
1282
  const data = await request(
1225
- `/api/matchmaking/wait?queue_id=${encodeURIComponent(queueResult.queueId)}`
1283
+ `/api/matchmaking/wait?queue_id=${encodeURIComponent(queueId)}`
1226
1284
  );
1227
1285
  if (data.matched && data.gameId) {
1228
1286
  output(data);
1229
1287
  return;
1230
1288
  }
1289
+ if (data.readyCheck && data.readyCheckId) {
1290
+ if (!data.alreadyConfirmed) {
1291
+ console.error(
1292
+ `Match found! Game: ${data.gameType ?? "unknown"}, players: ${data.playerCount ?? "?"}. Confirming ready...`
1293
+ );
1294
+ try {
1295
+ const readyResult = await request(
1296
+ "/api/matchmaking/ready",
1297
+ jsonBody({ readyCheckId: data.readyCheckId })
1298
+ );
1299
+ if (readyResult.allReady && readyResult.gameId) {
1300
+ output({ matched: true, gameId: readyResult.gameId, gameType: data.gameType });
1301
+ return;
1302
+ }
1303
+ console.error("Confirmed. Waiting for other players...");
1304
+ } catch {
1305
+ console.error("Ready check failed, returning to queue...");
1306
+ }
1307
+ }
1308
+ continue;
1309
+ }
1310
+ if (data.reason === "expired") {
1311
+ fail("Queue entry expired (stopped polling too long)");
1312
+ }
1231
1313
  } catch {
1232
1314
  await new Promise((r) => setTimeout(r, 2e3));
1233
1315
  }
1234
1316
  }
1317
+ }
1318
+ program.command("quickmatch").description("Queue and wait until matched (system picks the game)").option("--type <type>", "Vote for a game type (optional)").addOption(new Option("--max-players <n>", "(deprecated)").hideHelp()).action(async (opts) => {
1319
+ const body = {};
1320
+ if (opts.type) body.gameType = opts.type;
1321
+ const queueResult = await queueForMatch(body);
1322
+ if (queueResult.matched && queueResult.gameId) {
1323
+ output(queueResult);
1324
+ return;
1325
+ }
1326
+ await pollUntilMatched(queueResult.queueId);
1235
1327
  });
1236
1328
  function askConfirm(question) {
1237
1329
  if (!process.stdin.isTTY) {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@promptedgames/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
+ "packageManager": "pnpm@10.33.0",
4
5
  "description": "CLI for playing games on the Prompted platform. Build AI agents that play poker, Secret Hitler, Coup, Skull, and Liar's Dice.",
5
6
  "type": "module",
6
7
  "license": "MIT",