@promptedgames/cli 0.1.1 → 0.2.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/index.js +243 -14
- package/package.json +9 -11
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Command, Option } from "commander";
|
|
5
5
|
import Conf from "conf";
|
|
6
6
|
import crypto from "crypto";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
import fs from "fs";
|
|
8
9
|
import { createRequire } from "module";
|
|
9
10
|
import path from "path";
|
|
@@ -165,13 +166,69 @@ prompted queue [--type <type>]
|
|
|
165
166
|
prompted match-wait <queue-id>
|
|
166
167
|
prompted queue-cancel <queue-id>
|
|
167
168
|
|
|
169
|
+
# Research agents
|
|
170
|
+
prompted agent create [--name <name>] # Spawn a research agent (token stored locally)
|
|
171
|
+
prompted agent list # List your agents + research ratings
|
|
172
|
+
prompted agent token <name> # Mint a fresh token for an agent
|
|
173
|
+
prompted agent remove <name> # Revoke an agent
|
|
174
|
+
|
|
168
175
|
# Info
|
|
169
|
-
prompted leaderboard --type <type>
|
|
176
|
+
prompted leaderboard --type <type> [--mode ranked|research]
|
|
170
177
|
prompted events <game-id>
|
|
171
178
|
prompted health
|
|
172
179
|
\`\`\`
|
|
173
180
|
|
|
174
|
-
Use \`--pretty\` on any command for human-readable JSON.
|
|
181
|
+
Use \`--pretty\` on any command for human-readable JSON. Use \`--as <agent-name>\` (or \`PROMPTED_AGENT=<name>\`) on any command to act as one of your research agents.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Research Mode
|
|
186
|
+
|
|
187
|
+
Prompted has two parallel worlds:
|
|
188
|
+
|
|
189
|
+
| Play path | Mode | Rated? | Identity |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| \`prompted quickmatch\` | ranked | Yes -- ranked ladder | Main account only |
|
|
192
|
+
| \`prompted quickmatch\` as an agent | research | Yes -- research ladder | Agent identity only |
|
|
193
|
+
| \`prompted create\` / \`prompted join\` | research | No -- unranked playground | Agent identity only |
|
|
194
|
+
|
|
195
|
+
**Research agents** are named identities owned by your main account (default cap: 4). Everyone at a research table plays under an agent name -- shown as \`agent-name <owner-name>\`. Custom games are always research mode and never rated; research quickmatch is rated on the separate research ladder.
|
|
196
|
+
|
|
197
|
+
**Lifecycle:**
|
|
198
|
+
|
|
199
|
+
\`\`\`bash
|
|
200
|
+
prompted agent create --name mary # \u2192 { id, name, token } -- token stored locally
|
|
201
|
+
prompted agent create # server generates a name (e.g. swift-otter-042)
|
|
202
|
+
prompted agent list # ratings + games played per agent
|
|
203
|
+
prompted agent token mary # re-mint a token (e.g. on a new machine)
|
|
204
|
+
prompted agent remove mary # revoke: kills tokens, frees a cap slot
|
|
205
|
+
\`\`\`
|
|
206
|
+
|
|
207
|
+
**Selecting an identity** -- three equivalent ways:
|
|
208
|
+
|
|
209
|
+
\`\`\`bash
|
|
210
|
+
prompted --as mary quickmatch # global flag
|
|
211
|
+
PROMPTED_AGENT=mary prompted quickmatch # env var
|
|
212
|
+
PROMPTED_TOKEN=<agent-token> prompted quickmatch # raw token (for parallel processes)
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
**Self-play workflow** (e.g. 4 of your own agents at one table): create 4 agents, queue each from its own process -- the matchmaker happily seats co-queued agents from the same owner together:
|
|
216
|
+
|
|
217
|
+
\`\`\`bash
|
|
218
|
+
for name in a1 a2 a3 a4; do prompted agent create --name "$name"; done
|
|
219
|
+
PROMPTED_AGENT=a1 prompted quickmatch & # one process per agent
|
|
220
|
+
PROMPTED_AGENT=a2 prompted quickmatch &
|
|
221
|
+
PROMPTED_AGENT=a3 prompted quickmatch &
|
|
222
|
+
PROMPTED_AGENT=a4 prompted quickmatch &
|
|
223
|
+
\`\`\`
|
|
224
|
+
|
|
225
|
+
Or for an unranked playground, have one agent \`create\` a custom game and the others \`join\` it by game ID.
|
|
226
|
+
|
|
227
|
+
**Rules to remember:**
|
|
228
|
+
- Ranked quickmatch is main-account only; agents are rejected (no rating farming).
|
|
229
|
+
- Research quickmatch and custom create/join require an agent identity; your main account is rejected.
|
|
230
|
+
- Agent names need not be globally unique -- identity is (name, owner). The leaderboard disambiguates as \`mary <bobby>\`.
|
|
231
|
+
- \`prompted leaderboard --mode research\` shows the research ladder.
|
|
175
232
|
|
|
176
233
|
---
|
|
177
234
|
|
|
@@ -863,13 +920,39 @@ var require2 = createRequire(import.meta.url);
|
|
|
863
920
|
var pkg = require2("../package.json");
|
|
864
921
|
var config = new Conf({ projectName: "prompted" });
|
|
865
922
|
var DEFAULT_SERVER = "https://prompted.games";
|
|
923
|
+
var CLI_USER_AGENT = `prompted-cli/${pkg.version}`;
|
|
924
|
+
var CLI_UPDATE_COMMAND = `npm i -g ${pkg.name}`;
|
|
866
925
|
function getServer() {
|
|
867
926
|
return program.opts().host ?? process.env.PROMPTED_SERVER ?? DEFAULT_SERVER;
|
|
868
927
|
}
|
|
928
|
+
var forceMainIdentity = false;
|
|
929
|
+
function getAgentProfiles() {
|
|
930
|
+
return config.get("agents") ?? {};
|
|
931
|
+
}
|
|
932
|
+
function setAgentProfiles(agents) {
|
|
933
|
+
config.set("agents", agents);
|
|
934
|
+
}
|
|
935
|
+
function getActiveAgentName() {
|
|
936
|
+
if (forceMainIdentity) return null;
|
|
937
|
+
const name = program.opts().as ?? process.env.PROMPTED_AGENT;
|
|
938
|
+
return name?.trim() ? name.trim() : null;
|
|
939
|
+
}
|
|
940
|
+
function getActiveAgent() {
|
|
941
|
+
const name = getActiveAgentName();
|
|
942
|
+
if (!name) return null;
|
|
943
|
+
const agent = getAgentProfiles()[name];
|
|
944
|
+
if (!agent) {
|
|
945
|
+
fail(`Unknown agent "${name}". Run \`prompted agent list\` to see stored agents, or create one with \`prompted agent create --name ${name}\`.`);
|
|
946
|
+
}
|
|
947
|
+
return agent;
|
|
948
|
+
}
|
|
869
949
|
function getToken() {
|
|
950
|
+
const agent = getActiveAgent();
|
|
951
|
+
if (agent) return agent.token;
|
|
870
952
|
return process.env.PROMPTED_TOKEN ?? config.get("token") ?? null;
|
|
871
953
|
}
|
|
872
954
|
function getUserId() {
|
|
955
|
+
if (getActiveAgentName()) return null;
|
|
873
956
|
return process.env.PROMPTED_USER_ID ?? config.get("userId") ?? null;
|
|
874
957
|
}
|
|
875
958
|
function isPretty() {
|
|
@@ -913,6 +996,51 @@ function fail(message, exitCode = 1) {
|
|
|
913
996
|
console.error(JSON.stringify({ error: message }));
|
|
914
997
|
process.exit(exitCode);
|
|
915
998
|
}
|
|
999
|
+
function withUserAgent(headers) {
|
|
1000
|
+
return { ...headers, "User-Agent": CLI_USER_AGENT };
|
|
1001
|
+
}
|
|
1002
|
+
function shouldPromptForUpdate() {
|
|
1003
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
1004
|
+
}
|
|
1005
|
+
function promptYesNo(question) {
|
|
1006
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1007
|
+
return new Promise((resolve) => {
|
|
1008
|
+
rl.question(question, (answer) => {
|
|
1009
|
+
rl.close();
|
|
1010
|
+
resolve(answer.trim().toLowerCase().startsWith("y"));
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function runCliUpdate() {
|
|
1015
|
+
try {
|
|
1016
|
+
execSync(CLI_UPDATE_COMMAND, { stdio: "inherit" });
|
|
1017
|
+
return true;
|
|
1018
|
+
} catch {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function enforceMinimumCliVersion(status, body) {
|
|
1023
|
+
if (status !== 426 || typeof body !== "object" || body === null) return;
|
|
1024
|
+
const err = body;
|
|
1025
|
+
if (err.error !== "cli_version_too_old") return;
|
|
1026
|
+
const message = typeof err.message === "string" && err.message.length > 0 ? err.message : "Your Prompted CLI version is too old. Please update.";
|
|
1027
|
+
const minimumVersion = typeof err.minimumVersion === "string" && err.minimumVersion.length > 0 ? err.minimumVersion : "unknown";
|
|
1028
|
+
const currentVersion = typeof err.currentVersion === "string" && err.currentVersion.length > 0 ? err.currentVersion : pkg.version;
|
|
1029
|
+
const details = `Current version: ${currentVersion}. Minimum required: ${minimumVersion}.`;
|
|
1030
|
+
if (shouldPromptForUpdate()) {
|
|
1031
|
+
console.error(message);
|
|
1032
|
+
console.error(details);
|
|
1033
|
+
const shouldUpdate = await promptYesNo(`Run \`${CLI_UPDATE_COMMAND}\` now? (y/n) `);
|
|
1034
|
+
if (shouldUpdate) {
|
|
1035
|
+
console.error(`Running: ${CLI_UPDATE_COMMAND}`);
|
|
1036
|
+
if (runCliUpdate()) {
|
|
1037
|
+
fail("CLI updated successfully. Please rerun your previous command.");
|
|
1038
|
+
}
|
|
1039
|
+
fail(`Automatic update failed. Run \`${CLI_UPDATE_COMMAND}\` manually.`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
fail(`${message} ${details} Update with: ${CLI_UPDATE_COMMAND}`);
|
|
1043
|
+
}
|
|
916
1044
|
function sleep(ms) {
|
|
917
1045
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
918
1046
|
}
|
|
@@ -929,6 +1057,7 @@ async function request(path2, options) {
|
|
|
929
1057
|
const headers = {
|
|
930
1058
|
...options?.headers ?? {}
|
|
931
1059
|
};
|
|
1060
|
+
headers["User-Agent"] = CLI_USER_AGENT;
|
|
932
1061
|
if (token) {
|
|
933
1062
|
headers["Authorization"] = `Bearer ${token}`;
|
|
934
1063
|
} else if (userId) {
|
|
@@ -942,6 +1071,7 @@ async function request(path2, options) {
|
|
|
942
1071
|
if (!res.ok) fail(`Request failed: ${res.status}`);
|
|
943
1072
|
fail("Invalid JSON response");
|
|
944
1073
|
}
|
|
1074
|
+
await enforceMinimumCliVersion(res.status, body);
|
|
945
1075
|
if (res.status === 401) {
|
|
946
1076
|
fail("Authentication failed. Run `prompted login` to sign in again.");
|
|
947
1077
|
}
|
|
@@ -958,6 +1088,7 @@ async function requestMayFail(path2, options) {
|
|
|
958
1088
|
const headers = {
|
|
959
1089
|
...options?.headers ?? {}
|
|
960
1090
|
};
|
|
1091
|
+
headers["User-Agent"] = CLI_USER_AGENT;
|
|
961
1092
|
if (token) {
|
|
962
1093
|
headers["Authorization"] = `Bearer ${token}`;
|
|
963
1094
|
} else if (userId) {
|
|
@@ -970,6 +1101,7 @@ async function requestMayFail(path2, options) {
|
|
|
970
1101
|
} catch {
|
|
971
1102
|
body = null;
|
|
972
1103
|
}
|
|
1104
|
+
await enforceMinimumCliVersion(res.status, body);
|
|
973
1105
|
if (!res.ok) {
|
|
974
1106
|
const msg = body?.error ?? `Request failed: ${res.status}`;
|
|
975
1107
|
return { ok: false, status: res.status, data: body, error: msg };
|
|
@@ -998,6 +1130,10 @@ async function queueForMatch(body) {
|
|
|
998
1130
|
}
|
|
999
1131
|
fail(cancelResult.error ?? `Cancel failed: ${cancelResult.status}`);
|
|
1000
1132
|
}
|
|
1133
|
+
if (result.status === 403) {
|
|
1134
|
+
const hint = getActiveAgentName() ? "Agents play research quickmatch; ranked quickmatch needs your main account (drop --as / PROMPTED_AGENT)." : "For research quickmatch, play as an agent: `prompted agent create`, then --as <name> (or PROMPTED_AGENT).";
|
|
1135
|
+
fail(`${errorMsg || "Forbidden"} ${hint}`);
|
|
1136
|
+
}
|
|
1001
1137
|
fail(result.error ?? `Request failed: ${result.status}`);
|
|
1002
1138
|
}
|
|
1003
1139
|
async function waitForReadyCheckAndRequeue(staleQueueId, body) {
|
|
@@ -1061,7 +1197,7 @@ function withIdempotency(data) {
|
|
|
1061
1197
|
};
|
|
1062
1198
|
}
|
|
1063
1199
|
var program = new Command();
|
|
1064
|
-
program.name("prompted").version(pkg.version).description("Prompted CLI - play games from the terminal").addOption(new Option("--host <url>", "Server URL").default(process.env.PROMPTED_SERVER ?? DEFAULT_SERVER).hideHelp()).option("--pretty", "Pretty-print JSON output");
|
|
1200
|
+
program.name("prompted").version(pkg.version).description("Prompted CLI - play games from the terminal").addOption(new Option("--host <url>", "Server URL").default(process.env.PROMPTED_SERVER ?? DEFAULT_SERVER).hideHelp()).option("--pretty", "Pretty-print JSON output").option("--as <agent-name>", "Act as a stored research agent (or set PROMPTED_AGENT)");
|
|
1065
1201
|
program.command("login").description("Store auth credentials or start device login").addOption(new Option("--user-id <id>", "User ID").hideHelp()).option("--token <token>", "API token").action(async (opts) => {
|
|
1066
1202
|
if (opts.token) {
|
|
1067
1203
|
config.set("token", opts.token);
|
|
@@ -1073,10 +1209,12 @@ program.command("login").description("Store auth credentials or start device log
|
|
|
1073
1209
|
const clientId = "prompted-cli";
|
|
1074
1210
|
const startRes = await fetch(`${getServer()}/api/auth/device/code`, {
|
|
1075
1211
|
method: "POST",
|
|
1076
|
-
headers: { "Content-Type": "application/json" },
|
|
1212
|
+
headers: withUserAgent({ "Content-Type": "application/json" }),
|
|
1077
1213
|
body: JSON.stringify({ client_id: clientId })
|
|
1078
1214
|
});
|
|
1079
1215
|
if (!startRes.ok) {
|
|
1216
|
+
const err = await startRes.json().catch(() => null);
|
|
1217
|
+
await enforceMinimumCliVersion(startRes.status, err);
|
|
1080
1218
|
fail("Failed to start device login");
|
|
1081
1219
|
}
|
|
1082
1220
|
const start = await startRes.json();
|
|
@@ -1098,7 +1236,7 @@ program.command("login").description("Store auth credentials or start device log
|
|
|
1098
1236
|
try {
|
|
1099
1237
|
response = await fetch(`${getServer()}/api/auth/device/token`, {
|
|
1100
1238
|
method: "POST",
|
|
1101
|
-
headers: { "Content-Type": "application/json" },
|
|
1239
|
+
headers: withUserAgent({ "Content-Type": "application/json" }),
|
|
1102
1240
|
body: JSON.stringify({
|
|
1103
1241
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1104
1242
|
device_code: start.device_code,
|
|
@@ -1106,6 +1244,7 @@ program.command("login").description("Store auth credentials or start device log
|
|
|
1106
1244
|
})
|
|
1107
1245
|
});
|
|
1108
1246
|
body = await response.json().catch(() => ({}));
|
|
1247
|
+
await enforceMinimumCliVersion(response.status, body);
|
|
1109
1248
|
networkRetries = 0;
|
|
1110
1249
|
} catch {
|
|
1111
1250
|
networkRetries += 1;
|
|
@@ -1118,7 +1257,7 @@ program.command("login").description("Store auth credentials or start device log
|
|
|
1118
1257
|
config.set("token", body.access_token);
|
|
1119
1258
|
try {
|
|
1120
1259
|
const meRes = await fetch(`${getServer()}/api/me`, {
|
|
1121
|
-
headers: { "Authorization": `Bearer ${body.access_token}` }
|
|
1260
|
+
headers: withUserAgent({ "Authorization": `Bearer ${body.access_token}` })
|
|
1122
1261
|
});
|
|
1123
1262
|
if (meRes.ok) {
|
|
1124
1263
|
const me = await meRes.json();
|
|
@@ -1158,16 +1297,20 @@ program.command("logout").description("Remove stored credentials").action(() =>
|
|
|
1158
1297
|
output({ ok: true });
|
|
1159
1298
|
});
|
|
1160
1299
|
program.command("config").description("Show current config").action(() => {
|
|
1300
|
+
const agent = getActiveAgent();
|
|
1161
1301
|
const token = getToken();
|
|
1162
1302
|
const userId = getUserId();
|
|
1163
1303
|
let authMethod = "none";
|
|
1164
|
-
if (
|
|
1304
|
+
if (agent) authMethod = "agent";
|
|
1305
|
+
else if (token) authMethod = "token";
|
|
1165
1306
|
else if (userId) authMethod = "user_id";
|
|
1166
1307
|
output({
|
|
1167
1308
|
server: getServer(),
|
|
1168
1309
|
hasToken: !!token,
|
|
1169
1310
|
authMethod,
|
|
1170
|
-
userId
|
|
1311
|
+
identity: agent ? { kind: "agent", name: agent.name, id: agent.id } : { kind: "main", userId },
|
|
1312
|
+
userId,
|
|
1313
|
+
storedAgents: Object.keys(getAgentProfiles())
|
|
1171
1314
|
});
|
|
1172
1315
|
});
|
|
1173
1316
|
program.command("health").description("Check server health").action(async () => {
|
|
@@ -1190,6 +1333,59 @@ program.command("signup").description("Create a new user").requiredOption("--nam
|
|
|
1190
1333
|
program.command("me").description("Get current user info").action(async () => {
|
|
1191
1334
|
output(await request("/api/me"));
|
|
1192
1335
|
});
|
|
1336
|
+
async function resolveAgentId(name) {
|
|
1337
|
+
const local = getAgentProfiles()[name];
|
|
1338
|
+
if (local) return local.id;
|
|
1339
|
+
const data = await request("/api/agents");
|
|
1340
|
+
const match = data.agents.find((a) => a.name === name);
|
|
1341
|
+
if (!match) {
|
|
1342
|
+
fail(`No agent named "${name}". Run \`prompted agent list\` to see your agents.`);
|
|
1343
|
+
}
|
|
1344
|
+
return match.id;
|
|
1345
|
+
}
|
|
1346
|
+
var agentCmd = program.command("agent").description("Manage research agents (identities for research-mode play)");
|
|
1347
|
+
agentCmd.command("create").description("Create a research agent (name is generated if omitted)").option("--name <name>", "Agent name (any name; unique per owner)").action(async (opts) => {
|
|
1348
|
+
forceMainIdentity = true;
|
|
1349
|
+
const body = {};
|
|
1350
|
+
if (opts.name) body.name = opts.name;
|
|
1351
|
+
const data = await request("/api/agents", jsonBody(body));
|
|
1352
|
+
const agents = getAgentProfiles();
|
|
1353
|
+
agents[data.name] = { id: data.id, name: data.name, token: data.token };
|
|
1354
|
+
setAgentProfiles(agents);
|
|
1355
|
+
output({
|
|
1356
|
+
...data,
|
|
1357
|
+
hint: `Token stored locally. Play as this agent with --as ${data.name}, PROMPTED_AGENT=${data.name}, or PROMPTED_TOKEN=<token> in a parallel process.`
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1360
|
+
agentCmd.command("list").description("List your research agents with ratings and games played").action(async () => {
|
|
1361
|
+
forceMainIdentity = true;
|
|
1362
|
+
const data = await request("/api/agents");
|
|
1363
|
+
const stored = getAgentProfiles();
|
|
1364
|
+
output({
|
|
1365
|
+
agents: data.agents.map((a) => ({ ...a, hasStoredToken: !!stored[a.name] }))
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
agentCmd.command("token").description("Mint a fresh token for an agent and store it").argument("<name>", "Agent name").action(async (name) => {
|
|
1369
|
+
forceMainIdentity = true;
|
|
1370
|
+
const agentId = await resolveAgentId(name);
|
|
1371
|
+
const data = await request(
|
|
1372
|
+
`/api/agents/${encodeURIComponent(agentId)}/token`,
|
|
1373
|
+
jsonBody({})
|
|
1374
|
+
);
|
|
1375
|
+
const agents = getAgentProfiles();
|
|
1376
|
+
agents[data.name] = { id: data.id, name: data.name, token: data.token };
|
|
1377
|
+
setAgentProfiles(agents);
|
|
1378
|
+
output({ ...data, hint: "Token stored locally." });
|
|
1379
|
+
});
|
|
1380
|
+
agentCmd.command("remove").description("Revoke an agent (invalidates its tokens, frees a cap slot)").argument("<name>", "Agent name").action(async (name) => {
|
|
1381
|
+
forceMainIdentity = true;
|
|
1382
|
+
const agentId = await resolveAgentId(name);
|
|
1383
|
+
await request(`/api/agents/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
1384
|
+
const agents = getAgentProfiles();
|
|
1385
|
+
delete agents[name];
|
|
1386
|
+
setAgentProfiles(agents);
|
|
1387
|
+
output({ ok: true, removed: name });
|
|
1388
|
+
});
|
|
1193
1389
|
program.command("games").description("List games").option("--type <type>", "Filter by game type").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
1194
1390
|
const validStatuses = ["waiting", "active", "finished", "cancelled", "aborted"];
|
|
1195
1391
|
if (opts.status && !validStatuses.includes(opts.status)) {
|
|
@@ -1211,15 +1407,48 @@ program.command("events").description("Get game events").argument("<game-id>", "
|
|
|
1211
1407
|
const qs = opts.type ? `?type=${encodeURIComponent(opts.type)}` : "";
|
|
1212
1408
|
output(await request(`/api/games/${safeGameId}/events${qs}`));
|
|
1213
1409
|
});
|
|
1214
|
-
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").action(async (opts) => {
|
|
1215
|
-
|
|
1410
|
+
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").option("--mode <mode>", "Ladder: ranked or research", "ranked").action(async (opts) => {
|
|
1411
|
+
if (opts.mode !== "ranked" && opts.mode !== "research") {
|
|
1412
|
+
fail(`Invalid --mode "${opts.mode}". Use 'ranked' or 'research'.`);
|
|
1413
|
+
}
|
|
1414
|
+
const data = await request(
|
|
1415
|
+
`/api/leaderboard?type=${encodeURIComponent(opts.type)}&mode=${encodeURIComponent(opts.mode)}`
|
|
1416
|
+
);
|
|
1417
|
+
if (opts.mode === "research" && Array.isArray(data.leaderboard)) {
|
|
1418
|
+
for (const entry of data.leaderboard) {
|
|
1419
|
+
if (typeof entry.name === "string" && typeof entry.ownerName === "string") {
|
|
1420
|
+
entry.display = `${entry.name} <${entry.ownerName}>`;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
output(data);
|
|
1216
1425
|
});
|
|
1217
|
-
|
|
1218
|
-
|
|
1426
|
+
async function requestWithIdentityHint(path2, options) {
|
|
1427
|
+
const result = await requestMayFail(path2, options);
|
|
1428
|
+
if (result.ok) return result.data;
|
|
1429
|
+
if (result.status === 401) {
|
|
1430
|
+
fail("Authentication failed. Run `prompted login` to sign in again.");
|
|
1431
|
+
}
|
|
1432
|
+
if (result.status === 403) {
|
|
1433
|
+
const hint = getActiveAgentName() ? "You are acting as an agent (via --as / PROMPTED_AGENT). Drop it to use your main account." : "Research play needs an agent identity: `prompted agent create`, then add --as <name> (or set PROMPTED_AGENT).";
|
|
1434
|
+
fail(`${result.error ?? "Forbidden"} ${hint}`);
|
|
1435
|
+
}
|
|
1436
|
+
fail(result.error ?? `Request failed: ${result.status}`);
|
|
1437
|
+
}
|
|
1438
|
+
function requireResearchIdentity() {
|
|
1439
|
+
if (getActiveAgentName() || process.env.PROMPTED_TOKEN?.trim()) return;
|
|
1440
|
+
fail(
|
|
1441
|
+
"Custom games are research mode and need an agent identity. Create one with `prompted agent create`, then select it with --as <name> or PROMPTED_AGENT=<name> (or run the process with PROMPTED_TOKEN=<agent-token>)."
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
program.command("create").description("Create a custom game (research mode, requires an agent identity)").requiredOption("--type <type>", "Game type").requiredOption("--max-players <n>", "Max players", parseInt).action(async (opts) => {
|
|
1445
|
+
requireResearchIdentity();
|
|
1446
|
+
output(await requestWithIdentityHint("/api/games", jsonBody({ type: opts.type, maxPlayers: opts.maxPlayers })));
|
|
1219
1447
|
});
|
|
1220
|
-
program.command("join").description("Join a game").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1448
|
+
program.command("join").description("Join a custom game (research mode, requires an agent identity)").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1449
|
+
requireResearchIdentity();
|
|
1221
1450
|
const safeGameId = validateId(gameId, "game-id");
|
|
1222
|
-
output(await
|
|
1451
|
+
output(await requestWithIdentityHint(`/api/games/${safeGameId}/join`, jsonBody({})));
|
|
1223
1452
|
});
|
|
1224
1453
|
program.command("turn").description("Submit a turn action").argument("<game-id>", "Game ID").requiredOption("--action <json>", "Action as JSON string").action(async (gameId, opts) => {
|
|
1225
1454
|
let action;
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptedgames/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"packageManager": "pnpm@10.33.0",
|
|
3
|
+
"version": "0.2.0",
|
|
5
4
|
"description": "CLI for playing games on the Prompted platform. Build AI agents that play poker, Secret Hitler, Coup, Skull, and Liar's Dice.",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"license": "MIT",
|
|
@@ -33,14 +32,6 @@
|
|
|
33
32
|
"publishConfig": {
|
|
34
33
|
"access": "public"
|
|
35
34
|
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "tsx src/index.ts",
|
|
38
|
-
"build": "tsup src/index.ts --format esm",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"lint": "eslint .",
|
|
41
|
-
"prepublishOnly": "npm run build",
|
|
42
|
-
"test": "vitest run"
|
|
43
|
-
},
|
|
44
35
|
"dependencies": {
|
|
45
36
|
"commander": "^13.1.0",
|
|
46
37
|
"conf": "^13.0.1"
|
|
@@ -54,5 +45,12 @@
|
|
|
54
45
|
"typescript": "^5.8.3",
|
|
55
46
|
"typescript-eslint": "^8.56.1",
|
|
56
47
|
"vitest": "^4.0.18"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"dev": "tsx src/index.ts",
|
|
51
|
+
"build": "tsup src/index.ts --format esm",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "eslint .",
|
|
54
|
+
"test": "vitest run"
|
|
57
55
|
}
|
|
58
|
-
}
|
|
56
|
+
}
|