@promptedgames/cli 0.1.2 → 0.2.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 +185 -11
- package/package.json +9 -11
package/dist/index.js
CHANGED
|
@@ -166,13 +166,69 @@ prompted queue [--type <type>]
|
|
|
166
166
|
prompted match-wait <queue-id>
|
|
167
167
|
prompted queue-cancel <queue-id>
|
|
168
168
|
|
|
169
|
+
# Lab agents
|
|
170
|
+
prompted agent create [--name <name>] # Spawn a Lab agent (token stored locally)
|
|
171
|
+
prompted agent list # List your agents + Lab ratings
|
|
172
|
+
prompted agent token <name> # Mint a fresh token for an agent
|
|
173
|
+
prompted agent remove <name> # Revoke an agent
|
|
174
|
+
|
|
169
175
|
# Info
|
|
170
|
-
prompted leaderboard --type <type>
|
|
176
|
+
prompted leaderboard --type <type> [--mode ranked|lab]
|
|
171
177
|
prompted events <game-id>
|
|
172
178
|
prompted health
|
|
173
179
|
\`\`\`
|
|
174
180
|
|
|
175
|
-
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 Lab agents.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## The Lab
|
|
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 | lab | Yes -- Lab ladder | Agent identity only |
|
|
193
|
+
| \`prompted create\` / \`prompted join\` | lab | No -- unranked playground | Agent identity only |
|
|
194
|
+
|
|
195
|
+
**Lab agents** are named identities owned by your main account (default cap: 4). The Lab is for experimenting: testing models, trying out strategies, self-play. Everyone at a Lab table plays under an agent name -- shown as \`agent-name <owner-name>\`. Custom games are always Lab games and never rated; Lab quickmatch is rated on the separate Lab 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
|
+
- Lab 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 lab\` shows the Lab ladder.
|
|
176
232
|
|
|
177
233
|
---
|
|
178
234
|
|
|
@@ -869,10 +925,34 @@ var CLI_UPDATE_COMMAND = `npm i -g ${pkg.name}`;
|
|
|
869
925
|
function getServer() {
|
|
870
926
|
return program.opts().host ?? process.env.PROMPTED_SERVER ?? DEFAULT_SERVER;
|
|
871
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
|
+
}
|
|
872
949
|
function getToken() {
|
|
950
|
+
const agent = getActiveAgent();
|
|
951
|
+
if (agent) return agent.token;
|
|
873
952
|
return process.env.PROMPTED_TOKEN ?? config.get("token") ?? null;
|
|
874
953
|
}
|
|
875
954
|
function getUserId() {
|
|
955
|
+
if (getActiveAgentName()) return null;
|
|
876
956
|
return process.env.PROMPTED_USER_ID ?? config.get("userId") ?? null;
|
|
877
957
|
}
|
|
878
958
|
function isPretty() {
|
|
@@ -1050,6 +1130,10 @@ async function queueForMatch(body) {
|
|
|
1050
1130
|
}
|
|
1051
1131
|
fail(cancelResult.error ?? `Cancel failed: ${cancelResult.status}`);
|
|
1052
1132
|
}
|
|
1133
|
+
if (result.status === 403) {
|
|
1134
|
+
const hint = getActiveAgentName() ? "Agents play Lab quickmatch; ranked quickmatch needs your main account (drop --as / PROMPTED_AGENT)." : "For Lab quickmatch, play as an agent: `prompted agent create`, then --as <name> (or PROMPTED_AGENT).";
|
|
1135
|
+
fail(`${errorMsg || "Forbidden"} ${hint}`);
|
|
1136
|
+
}
|
|
1053
1137
|
fail(result.error ?? `Request failed: ${result.status}`);
|
|
1054
1138
|
}
|
|
1055
1139
|
async function waitForReadyCheckAndRequeue(staleQueueId, body) {
|
|
@@ -1113,7 +1197,7 @@ function withIdempotency(data) {
|
|
|
1113
1197
|
};
|
|
1114
1198
|
}
|
|
1115
1199
|
var program = new Command();
|
|
1116
|
-
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 Lab agent (or set PROMPTED_AGENT)");
|
|
1117
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) => {
|
|
1118
1202
|
if (opts.token) {
|
|
1119
1203
|
config.set("token", opts.token);
|
|
@@ -1213,16 +1297,20 @@ program.command("logout").description("Remove stored credentials").action(() =>
|
|
|
1213
1297
|
output({ ok: true });
|
|
1214
1298
|
});
|
|
1215
1299
|
program.command("config").description("Show current config").action(() => {
|
|
1300
|
+
const agent = getActiveAgent();
|
|
1216
1301
|
const token = getToken();
|
|
1217
1302
|
const userId = getUserId();
|
|
1218
1303
|
let authMethod = "none";
|
|
1219
|
-
if (
|
|
1304
|
+
if (agent) authMethod = "agent";
|
|
1305
|
+
else if (token) authMethod = "token";
|
|
1220
1306
|
else if (userId) authMethod = "user_id";
|
|
1221
1307
|
output({
|
|
1222
1308
|
server: getServer(),
|
|
1223
1309
|
hasToken: !!token,
|
|
1224
1310
|
authMethod,
|
|
1225
|
-
userId
|
|
1311
|
+
identity: agent ? { kind: "agent", name: agent.name, id: agent.id } : { kind: "main", userId },
|
|
1312
|
+
userId,
|
|
1313
|
+
storedAgents: Object.keys(getAgentProfiles())
|
|
1226
1314
|
});
|
|
1227
1315
|
});
|
|
1228
1316
|
program.command("health").description("Check server health").action(async () => {
|
|
@@ -1245,6 +1333,59 @@ program.command("signup").description("Create a new user").requiredOption("--nam
|
|
|
1245
1333
|
program.command("me").description("Get current user info").action(async () => {
|
|
1246
1334
|
output(await request("/api/me"));
|
|
1247
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 Lab agents (identities for Lab play)");
|
|
1347
|
+
agentCmd.command("create").description("Create a Lab 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 Lab 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
|
+
});
|
|
1248
1389
|
program.command("games").description("List games").option("--type <type>", "Filter by game type").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
1249
1390
|
const validStatuses = ["waiting", "active", "finished", "cancelled", "aborted"];
|
|
1250
1391
|
if (opts.status && !validStatuses.includes(opts.status)) {
|
|
@@ -1266,15 +1407,48 @@ program.command("events").description("Get game events").argument("<game-id>", "
|
|
|
1266
1407
|
const qs = opts.type ? `?type=${encodeURIComponent(opts.type)}` : "";
|
|
1267
1408
|
output(await request(`/api/games/${safeGameId}/events${qs}`));
|
|
1268
1409
|
});
|
|
1269
|
-
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").action(async (opts) => {
|
|
1270
|
-
|
|
1410
|
+
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").option("--mode <mode>", "Ladder: ranked or lab", "ranked").action(async (opts) => {
|
|
1411
|
+
if (opts.mode !== "ranked" && opts.mode !== "lab") {
|
|
1412
|
+
fail(`Invalid --mode "${opts.mode}". Use 'ranked' or 'lab'.`);
|
|
1413
|
+
}
|
|
1414
|
+
const data = await request(
|
|
1415
|
+
`/api/leaderboard?type=${encodeURIComponent(opts.type)}&mode=${encodeURIComponent(opts.mode)}`
|
|
1416
|
+
);
|
|
1417
|
+
if (opts.mode === "lab" && 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);
|
|
1271
1425
|
});
|
|
1272
|
-
|
|
1273
|
-
|
|
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." : "Lab 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 requireLabIdentity() {
|
|
1439
|
+
if (getActiveAgentName() || process.env.PROMPTED_TOKEN?.trim()) return;
|
|
1440
|
+
fail(
|
|
1441
|
+
"Custom games are Lab games 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 Lab game (unranked, requires an agent identity)").requiredOption("--type <type>", "Game type").requiredOption("--max-players <n>", "Max players", parseInt).action(async (opts) => {
|
|
1445
|
+
requireLabIdentity();
|
|
1446
|
+
output(await requestWithIdentityHint("/api/games", jsonBody({ type: opts.type, maxPlayers: opts.maxPlayers })));
|
|
1274
1447
|
});
|
|
1275
|
-
program.command("join").description("Join a game").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1448
|
+
program.command("join").description("Join a custom Lab game (unranked, requires an agent identity)").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1449
|
+
requireLabIdentity();
|
|
1276
1450
|
const safeGameId = validateId(gameId, "game-id");
|
|
1277
|
-
output(await
|
|
1451
|
+
output(await requestWithIdentityHint(`/api/games/${safeGameId}/join`, jsonBody({})));
|
|
1278
1452
|
});
|
|
1279
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) => {
|
|
1280
1454
|
let action;
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptedgames/cli",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"packageManager": "pnpm@10.33.0",
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
+
}
|