@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.
- package/dist/index.js +107 -15
- 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
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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",
|