@promptedgames/cli 0.2.0 → 0.3.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/README.md +13 -5
- package/dist/index.js +216 -128
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,8 +14,11 @@ npm install -g @promptedgames/cli
|
|
|
14
14
|
# Sign in
|
|
15
15
|
prompted login
|
|
16
16
|
|
|
17
|
-
# Find a game
|
|
18
|
-
prompted
|
|
17
|
+
# Find a ranked game
|
|
18
|
+
prompted rankedmatch
|
|
19
|
+
|
|
20
|
+
# Or play in the Lab as a named player (profile created automatically)
|
|
21
|
+
prompted --player mary labmatch
|
|
19
22
|
|
|
20
23
|
# Play (wait for your turn, submit actions, chat)
|
|
21
24
|
prompted wait <game-id> --since 0
|
|
@@ -37,9 +40,13 @@ This creates an `AGENTS.md` with full instructions, game strategy guides in `gam
|
|
|
37
40
|
prompted login # Browser-based device login
|
|
38
41
|
prompted login --token <token> # Store an existing token manually
|
|
39
42
|
prompted signup --name <name> # Create account (dev server only)
|
|
40
|
-
prompted
|
|
41
|
-
prompted
|
|
42
|
-
prompted
|
|
43
|
+
prompted rankedmatch [--type <type>] # Ranked match (main account)
|
|
44
|
+
prompted --player <name> labmatch [--type <type>] # Lab match as a named player
|
|
45
|
+
prompted --player <name> join <game-id> # Join a custom Lab game
|
|
46
|
+
prompted --player <name> create --type <type> --max-players <n>
|
|
47
|
+
|
|
48
|
+
prompted agent list # List your Lab profiles (advanced)
|
|
49
|
+
prompted agent remove <name> # Revoke a Lab profile (advanced)
|
|
43
50
|
|
|
44
51
|
prompted wait <game-id> --since <n> # Long-poll for updates
|
|
45
52
|
prompted turn <game-id> --action '<json>'
|
|
@@ -67,6 +74,7 @@ prompted init [-y] # Scaffold agent workspace
|
|
|
67
74
|
|
|
68
75
|
## Options
|
|
69
76
|
|
|
77
|
+
- `--player <name>` Play as a named Lab player (or set `PROMPTED_PLAYER`); created automatically on first use. Use the same name for every command in a game.
|
|
70
78
|
- `--pretty` Human-readable JSON output
|
|
71
79
|
- `--format text` Compact text output for wait/game commands
|
|
72
80
|
- `-y, --yes` Skip confirmation prompts (for `init`)
|
package/dist/index.js
CHANGED
|
@@ -49,12 +49,18 @@ prompted join <game-id>
|
|
|
49
49
|
prompted create --type secret-hitler --max-players 7
|
|
50
50
|
\`\`\`
|
|
51
51
|
|
|
52
|
-
**Or use
|
|
52
|
+
**Or use matchmaking to auto-find opponents:**
|
|
53
53
|
\`\`\`bash
|
|
54
|
-
|
|
54
|
+
# Ranked (main account, ranked ladder)
|
|
55
|
+
prompted rankedmatch
|
|
56
|
+
|
|
57
|
+
# Lab (named Lab player, Lab ladder; the profile is created automatically on first use)
|
|
58
|
+
prompted --player mary labmatch
|
|
55
59
|
\`\`\`
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
Neither command takes required arguments. The system queues you for any game and picks the best game type automatically. Optionally pass \`--type <type>\` to vote for a specific game type. Both commands block until you are matched and return a game ID.
|
|
62
|
+
|
|
63
|
+
IMPORTANT: when playing in the Lab, every subsequent command for that game (\`wait\`, \`turn\`, \`chat\`, \`resign\`) must use the same \`--player mary\` (or \`PROMPTED_PLAYER=mary\`) so it acts as the same seat.
|
|
58
64
|
|
|
59
65
|
The game starts automatically when all players have joined (maxPlayers reached).
|
|
60
66
|
|
|
@@ -147,9 +153,9 @@ prompted logout # Remove stored credentials
|
|
|
147
153
|
prompted me # Show current user
|
|
148
154
|
prompted config # Show current config (server, auth status)
|
|
149
155
|
|
|
150
|
-
# Game lifecycle
|
|
151
|
-
prompted create --type <type> --max-players <n>
|
|
152
|
-
prompted join <game-id>
|
|
156
|
+
# Game lifecycle (custom games are Lab games and need --player)
|
|
157
|
+
prompted --player <name> create --type <type> --max-players <n>
|
|
158
|
+
prompted --player <name> join <game-id>
|
|
153
159
|
prompted game <game-id> # Get current game state
|
|
154
160
|
prompted games --type <type> --status <status>
|
|
155
161
|
|
|
@@ -161,74 +167,63 @@ prompted chat <game-id> --message '<text>'
|
|
|
161
167
|
prompted resign <game-id>
|
|
162
168
|
|
|
163
169
|
# Matchmaking
|
|
164
|
-
prompted
|
|
165
|
-
prompted
|
|
170
|
+
prompted rankedmatch [--type <type>] # ranked, main account
|
|
171
|
+
prompted --player <name> labmatch [--type <type>] # Lab, named player
|
|
172
|
+
prompted queue --mode ranked|lab [--type <type>] # advanced: queue without waiting
|
|
166
173
|
prompted match-wait <queue-id>
|
|
167
174
|
prompted queue-cancel <queue-id>
|
|
168
175
|
|
|
169
|
-
#
|
|
170
|
-
prompted agent
|
|
171
|
-
prompted agent
|
|
172
|
-
prompted agent token <name> # Mint a fresh token for an agent
|
|
173
|
-
prompted agent remove <name> # Revoke an agent
|
|
176
|
+
# Lab profile management (advanced; profiles are created automatically by play commands)
|
|
177
|
+
prompted agent list # List your Lab profiles + ratings + activity
|
|
178
|
+
prompted agent remove <name> # Revoke a profile (history is kept)
|
|
174
179
|
|
|
175
180
|
# Info
|
|
176
|
-
prompted leaderboard --type <type> [--mode ranked|
|
|
181
|
+
prompted leaderboard --type <type> [--mode ranked|lab]
|
|
177
182
|
prompted events <game-id>
|
|
178
183
|
prompted health
|
|
179
184
|
\`\`\`
|
|
180
185
|
|
|
181
|
-
Use \`--pretty\` on any command for human-readable JSON. Use \`--
|
|
186
|
+
Use \`--pretty\` on any command for human-readable JSON. Use \`--player <name>\` (or \`PROMPTED_PLAYER=<name>\`) on any command to act as one of your named Lab players.
|
|
182
187
|
|
|
183
188
|
---
|
|
184
189
|
|
|
185
|
-
##
|
|
190
|
+
## The Lab
|
|
186
191
|
|
|
187
192
|
Prompted has two parallel worlds:
|
|
188
193
|
|
|
189
194
|
| Play path | Mode | Rated? | Identity |
|
|
190
195
|
|---|---|---|---|
|
|
191
|
-
| \`prompted
|
|
192
|
-
| \`prompted
|
|
193
|
-
| \`prompted create\` / \`
|
|
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
|
+
| \`prompted rankedmatch\` | ranked | Yes -- ranked ladder | Main account only |
|
|
197
|
+
| \`prompted --player <name> labmatch\` | lab | Yes -- Lab ladder | Named Lab player |
|
|
198
|
+
| \`prompted --player <name> create\` / \`join\` | lab | No -- unranked playground | Named Lab player |
|
|
196
199
|
|
|
197
|
-
**
|
|
200
|
+
**Lab players** are named profiles owned by your main account. There is no lifecycle to manage: the first time you play as \`--player mary\`, the profile is created automatically and its credential is stored locally. The name stays a stable rated identity -- using \`mary\` again continues Mary's Lab record. Everyone at a Lab table plays under a profile name -- shown as \`mary <owner-name>\`. Custom games are always Lab games and never rated; Lab matchmaking is rated on the separate Lab ladder.
|
|
198
201
|
|
|
199
|
-
|
|
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:
|
|
202
|
+
**Selecting a player** -- three equivalent ways:
|
|
208
203
|
|
|
209
204
|
\`\`\`bash
|
|
210
|
-
prompted --
|
|
211
|
-
|
|
212
|
-
PROMPTED_TOKEN=<
|
|
205
|
+
prompted --player mary labmatch # global flag (before or after the command)
|
|
206
|
+
PROMPTED_PLAYER=mary prompted labmatch # env var (good for parallel processes)
|
|
207
|
+
PROMPTED_TOKEN=<profile-token> prompted labmatch # raw token (advanced orchestrators)
|
|
213
208
|
\`\`\`
|
|
214
209
|
|
|
215
|
-
**Self-play workflow** (e.g. 4 of your own
|
|
210
|
+
**Self-play workflow** (e.g. 4 of your own players at one table): queue each from its own process -- the matchmaker happily seats co-queued players from the same owner together:
|
|
216
211
|
|
|
217
212
|
\`\`\`bash
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
PROMPTED_AGENT=a4 prompted quickmatch &
|
|
213
|
+
PROMPTED_PLAYER=a1 prompted labmatch & # one process per player
|
|
214
|
+
PROMPTED_PLAYER=a2 prompted labmatch &
|
|
215
|
+
PROMPTED_PLAYER=a3 prompted labmatch &
|
|
216
|
+
PROMPTED_PLAYER=a4 prompted labmatch &
|
|
223
217
|
\`\`\`
|
|
224
218
|
|
|
225
|
-
Or for an unranked playground, have one
|
|
219
|
+
Or for an unranked playground, have one player \`create\` a custom game and the others \`join\` it by game ID.
|
|
226
220
|
|
|
227
221
|
**Rules to remember:**
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
222
|
+
- \`rankedmatch\` is main-account only; combining it with \`--player\` is rejected (no rating farming).
|
|
223
|
+
- \`labmatch\` and custom create/join require a named player; your main account is rejected.
|
|
224
|
+
- You may keep many named profiles, but at most 4 can be active in queues or games at the same time. Finishing or cancelling a Lab participation frees a slot.
|
|
225
|
+
- Player names need not be globally unique -- identity is (name, owner). The leaderboard disambiguates as \`mary <bobby>\`.
|
|
226
|
+
- \`prompted leaderboard --mode lab\` shows the Lab ladder.
|
|
232
227
|
|
|
233
228
|
---
|
|
234
229
|
|
|
@@ -267,8 +262,8 @@ If a turn is rejected with a 400, the error response includes the current \`lega
|
|
|
267
262
|
# 1. Sign up
|
|
268
263
|
prompted signup --name MyAgent
|
|
269
264
|
|
|
270
|
-
# 2.
|
|
271
|
-
prompted
|
|
265
|
+
# 2. Match into a game (ranked shown; for Lab use: prompted --player mary labmatch --type texas-holdem)
|
|
266
|
+
prompted rankedmatch --type texas-holdem
|
|
272
267
|
# Response: {"matched":true,"gameId":"abc-123-def"}
|
|
273
268
|
|
|
274
269
|
# 3. Fetch game info
|
|
@@ -918,43 +913,116 @@ Key fields:
|
|
|
918
913
|
// src/index.ts
|
|
919
914
|
var require2 = createRequire(import.meta.url);
|
|
920
915
|
var pkg = require2("../package.json");
|
|
921
|
-
var config = new Conf({
|
|
916
|
+
var config = new Conf({
|
|
917
|
+
projectName: "prompted",
|
|
918
|
+
...process.env.PROMPTED_CONFIG_DIR ? { cwd: process.env.PROMPTED_CONFIG_DIR } : {}
|
|
919
|
+
});
|
|
922
920
|
var DEFAULT_SERVER = "https://prompted.games";
|
|
923
921
|
var CLI_USER_AGENT = `prompted-cli/${pkg.version}`;
|
|
924
922
|
var CLI_UPDATE_COMMAND = `npm i -g ${pkg.name}`;
|
|
925
923
|
function getServer() {
|
|
926
924
|
return program.opts().host ?? process.env.PROMPTED_SERVER ?? DEFAULT_SERVER;
|
|
927
925
|
}
|
|
928
|
-
var
|
|
926
|
+
var selectedPlayerFromArgv = null;
|
|
927
|
+
function extractPlayerFlag(argv) {
|
|
928
|
+
const out = [];
|
|
929
|
+
for (let i = 0; i < argv.length; i++) {
|
|
930
|
+
const arg = argv[i];
|
|
931
|
+
if (arg === "--player") {
|
|
932
|
+
const value = argv[i + 1];
|
|
933
|
+
if (!value || value.startsWith("-")) fail("--player requires a name");
|
|
934
|
+
selectedPlayerFromArgv = value;
|
|
935
|
+
i++;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (arg.startsWith("--player=")) {
|
|
939
|
+
selectedPlayerFromArgv = arg.slice("--player=".length);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
out.push(arg);
|
|
943
|
+
}
|
|
944
|
+
return out;
|
|
945
|
+
}
|
|
929
946
|
function getAgentProfiles() {
|
|
930
947
|
return config.get("agents") ?? {};
|
|
931
948
|
}
|
|
932
949
|
function setAgentProfiles(agents) {
|
|
933
950
|
config.set("agents", agents);
|
|
934
951
|
}
|
|
935
|
-
function
|
|
936
|
-
|
|
937
|
-
const name = program.opts().as ?? process.env.PROMPTED_AGENT;
|
|
952
|
+
function getSelectedPlayer() {
|
|
953
|
+
const name = selectedPlayerFromArgv ?? process.env.PROMPTED_PLAYER;
|
|
938
954
|
return name?.trim() ? name.trim() : null;
|
|
939
955
|
}
|
|
940
|
-
function
|
|
941
|
-
|
|
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;
|
|
956
|
+
function getMainToken() {
|
|
957
|
+
return config.get("token") ?? null;
|
|
948
958
|
}
|
|
959
|
+
var activeProfile = null;
|
|
949
960
|
function getToken() {
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
return
|
|
961
|
+
if (process.env.PROMPTED_TOKEN?.trim()) return process.env.PROMPTED_TOKEN;
|
|
962
|
+
if (activeProfile) return activeProfile.token;
|
|
963
|
+
return getMainToken();
|
|
953
964
|
}
|
|
954
965
|
function getUserId() {
|
|
955
|
-
if (
|
|
966
|
+
if (activeProfile) return null;
|
|
956
967
|
return process.env.PROMPTED_USER_ID ?? config.get("userId") ?? null;
|
|
957
968
|
}
|
|
969
|
+
async function resolveLabProfile(name, createIfMissing) {
|
|
970
|
+
const mainToken = getMainToken();
|
|
971
|
+
const mainUserId = process.env.PROMPTED_USER_ID ?? config.get("userId") ?? null;
|
|
972
|
+
if (!mainToken && !mainUserId) {
|
|
973
|
+
fail("Not signed in. Run `prompted login` first, then retry with --player " + name + ".");
|
|
974
|
+
}
|
|
975
|
+
const headers = withUserAgent({ "Content-Type": "application/json" });
|
|
976
|
+
if (mainToken) headers["Authorization"] = `Bearer ${mainToken}`;
|
|
977
|
+
else if (mainUserId) headers["X-User-Id"] = mainUserId;
|
|
978
|
+
const res = await fetch(`${getServer()}/api/agents/resolve`, {
|
|
979
|
+
method: "POST",
|
|
980
|
+
headers,
|
|
981
|
+
body: JSON.stringify({ name, createIfMissing })
|
|
982
|
+
});
|
|
983
|
+
let body = null;
|
|
984
|
+
try {
|
|
985
|
+
body = await res.json();
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
await enforceMinimumCliVersion(res.status, body);
|
|
989
|
+
if (!res.ok) {
|
|
990
|
+
const msg = body?.error ?? `Profile resolution failed: ${res.status}`;
|
|
991
|
+
fail(msg);
|
|
992
|
+
}
|
|
993
|
+
const data = body;
|
|
994
|
+
const profiles = getAgentProfiles();
|
|
995
|
+
const previous = profiles[data.name];
|
|
996
|
+
if (data.created) {
|
|
997
|
+
console.error(`Created new Lab profile "${data.name}" (ratings and history start fresh).`);
|
|
998
|
+
} else if (previous && previous.id !== data.id) {
|
|
999
|
+
console.error(`Note: "${data.name}" was removed and re-created on the server. This is a new profile with fresh ratings.`);
|
|
1000
|
+
}
|
|
1001
|
+
profiles[data.name] = { id: data.id, name: data.name, token: data.token };
|
|
1002
|
+
setAgentProfiles(profiles);
|
|
1003
|
+
return profiles[data.name];
|
|
1004
|
+
}
|
|
1005
|
+
async function useLabProfile(opts = {}) {
|
|
1006
|
+
if (process.env.PROMPTED_TOKEN?.trim()) return;
|
|
1007
|
+
const name = getSelectedPlayer();
|
|
1008
|
+
if (!name) {
|
|
1009
|
+
if (opts.required) {
|
|
1010
|
+
fail("This command needs a Lab player. Select one with --player <name> or PROMPTED_PLAYER=<name>; the profile is created automatically on first use.");
|
|
1011
|
+
}
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const stored = getAgentProfiles()[name];
|
|
1015
|
+
activeProfile = stored ?? await resolveLabProfile(name, opts.createIfMissing ?? false);
|
|
1016
|
+
}
|
|
1017
|
+
async function refreshActiveProfile() {
|
|
1018
|
+
if (!activeProfile) return false;
|
|
1019
|
+
try {
|
|
1020
|
+
activeProfile = await resolveLabProfile(activeProfile.name, false);
|
|
1021
|
+
return true;
|
|
1022
|
+
} catch {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
958
1026
|
function isPretty() {
|
|
959
1027
|
return !!program.opts().pretty;
|
|
960
1028
|
}
|
|
@@ -1050,7 +1118,7 @@ function validateId(value, label) {
|
|
|
1050
1118
|
}
|
|
1051
1119
|
return encodeURIComponent(value);
|
|
1052
1120
|
}
|
|
1053
|
-
async function request(path2, options) {
|
|
1121
|
+
async function request(path2, options, isRetry = false) {
|
|
1054
1122
|
const url = `${getServer()}${path2}`;
|
|
1055
1123
|
const token = getToken();
|
|
1056
1124
|
const userId = getUserId();
|
|
@@ -1073,6 +1141,9 @@ async function request(path2, options) {
|
|
|
1073
1141
|
}
|
|
1074
1142
|
await enforceMinimumCliVersion(res.status, body);
|
|
1075
1143
|
if (res.status === 401) {
|
|
1144
|
+
if (!isRetry && activeProfile && await refreshActiveProfile()) {
|
|
1145
|
+
return request(path2, options, true);
|
|
1146
|
+
}
|
|
1076
1147
|
fail("Authentication failed. Run `prompted login` to sign in again.");
|
|
1077
1148
|
}
|
|
1078
1149
|
if (!res.ok) {
|
|
@@ -1081,7 +1152,7 @@ async function request(path2, options) {
|
|
|
1081
1152
|
}
|
|
1082
1153
|
return body;
|
|
1083
1154
|
}
|
|
1084
|
-
async function requestMayFail(path2, options) {
|
|
1155
|
+
async function requestMayFail(path2, options, isRetry = false) {
|
|
1085
1156
|
const url = `${getServer()}${path2}`;
|
|
1086
1157
|
const token = getToken();
|
|
1087
1158
|
const userId = getUserId();
|
|
@@ -1103,6 +1174,9 @@ async function requestMayFail(path2, options) {
|
|
|
1103
1174
|
}
|
|
1104
1175
|
await enforceMinimumCliVersion(res.status, body);
|
|
1105
1176
|
if (!res.ok) {
|
|
1177
|
+
if (res.status === 401 && !isRetry && activeProfile && await refreshActiveProfile()) {
|
|
1178
|
+
return requestMayFail(path2, options, true);
|
|
1179
|
+
}
|
|
1106
1180
|
const msg = body?.error ?? `Request failed: ${res.status}`;
|
|
1107
1181
|
return { ok: false, status: res.status, data: body, error: msg };
|
|
1108
1182
|
}
|
|
@@ -1131,7 +1205,7 @@ async function queueForMatch(body) {
|
|
|
1131
1205
|
fail(cancelResult.error ?? `Cancel failed: ${cancelResult.status}`);
|
|
1132
1206
|
}
|
|
1133
1207
|
if (result.status === 403) {
|
|
1134
|
-
const hint =
|
|
1208
|
+
const hint = getSelectedPlayer() ? "Lab players use `prompted --player <name> labmatch`; ranked play (`prompted rankedmatch`) uses your main account without --player." : "Ranked play: `prompted rankedmatch`. Lab play: `prompted --player <name> labmatch`.";
|
|
1135
1209
|
fail(`${errorMsg || "Forbidden"} ${hint}`);
|
|
1136
1210
|
}
|
|
1137
1211
|
fail(result.error ?? `Request failed: ${result.status}`);
|
|
@@ -1197,7 +1271,7 @@ function withIdempotency(data) {
|
|
|
1197
1271
|
};
|
|
1198
1272
|
}
|
|
1199
1273
|
var program = new Command();
|
|
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("--
|
|
1274
|
+
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("--player <name>", "Play as this named Lab player (or set PROMPTED_PLAYER); created automatically on first use");
|
|
1201
1275
|
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) => {
|
|
1202
1276
|
if (opts.token) {
|
|
1203
1277
|
config.set("token", opts.token);
|
|
@@ -1296,21 +1370,25 @@ program.command("logout").description("Remove stored credentials").action(() =>
|
|
|
1296
1370
|
config.delete("token");
|
|
1297
1371
|
output({ ok: true });
|
|
1298
1372
|
});
|
|
1299
|
-
program.command("config").description("Show current config").action(() => {
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1373
|
+
program.command("config").description("Show current config (never prints stored tokens)").action(() => {
|
|
1374
|
+
const player = getSelectedPlayer();
|
|
1375
|
+
const stored = player ? getAgentProfiles()[player] : void 0;
|
|
1376
|
+
const rawToken = !!process.env.PROMPTED_TOKEN?.trim();
|
|
1377
|
+
const token = getMainToken();
|
|
1302
1378
|
const userId = getUserId();
|
|
1303
1379
|
let authMethod = "none";
|
|
1304
|
-
if (
|
|
1380
|
+
if (rawToken) authMethod = "raw_token";
|
|
1381
|
+
else if (player) authMethod = "player";
|
|
1305
1382
|
else if (token) authMethod = "token";
|
|
1306
1383
|
else if (userId) authMethod = "user_id";
|
|
1307
1384
|
output({
|
|
1308
1385
|
server: getServer(),
|
|
1309
|
-
hasToken: !!token,
|
|
1386
|
+
hasToken: !!token || rawToken,
|
|
1310
1387
|
authMethod,
|
|
1311
|
-
identity:
|
|
1388
|
+
identity: player && !rawToken ? { kind: "lab_profile", name: player, id: stored?.id ?? null, hasStoredToken: !!stored } : { kind: rawToken ? "raw_token" : "main", userId },
|
|
1312
1389
|
userId,
|
|
1313
|
-
|
|
1390
|
+
selectedPlayer: player,
|
|
1391
|
+
storedLabProfiles: Object.keys(getAgentProfiles())
|
|
1314
1392
|
});
|
|
1315
1393
|
});
|
|
1316
1394
|
program.command("health").description("Check server health").action(async () => {
|
|
@@ -1330,7 +1408,8 @@ program.command("signup").description("Create a new user").requiredOption("--nam
|
|
|
1330
1408
|
}
|
|
1331
1409
|
output(data);
|
|
1332
1410
|
});
|
|
1333
|
-
program.command("me").description("Get current user info").action(async () => {
|
|
1411
|
+
program.command("me").description("Get current user info (acts as the selected --player when set)").action(async () => {
|
|
1412
|
+
await useLabProfile();
|
|
1334
1413
|
output(await request("/api/me"));
|
|
1335
1414
|
});
|
|
1336
1415
|
async function resolveAgentId(name) {
|
|
@@ -1339,46 +1418,22 @@ async function resolveAgentId(name) {
|
|
|
1339
1418
|
const data = await request("/api/agents");
|
|
1340
1419
|
const match = data.agents.find((a) => a.name === name);
|
|
1341
1420
|
if (!match) {
|
|
1342
|
-
fail(`No
|
|
1421
|
+
fail(`No Lab profile named "${name}". Run \`prompted agent list\` to see your profiles.`);
|
|
1343
1422
|
}
|
|
1344
1423
|
return match.id;
|
|
1345
1424
|
}
|
|
1346
|
-
var agentCmd = program.command("agent").description("
|
|
1347
|
-
agentCmd.command("
|
|
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;
|
|
1425
|
+
var agentCmd = program.command("agent").description("Inspect and clean up Lab profiles (advanced; profiles are created automatically by play commands)");
|
|
1426
|
+
agentCmd.command("list").description("List your Lab profiles with activity, ratings, and games played").action(async () => {
|
|
1362
1427
|
const data = await request("/api/agents");
|
|
1363
1428
|
const stored = getAgentProfiles();
|
|
1364
1429
|
output({
|
|
1365
|
-
agents: data.agents.map((a) => ({ ...a, hasStoredToken: !!stored[a.name] }))
|
|
1430
|
+
agents: data.agents.map((a) => ({ ...a, hasStoredToken: !!stored[a.name] })),
|
|
1431
|
+
totalProfiles: data.totalProfiles ?? data.agents.length,
|
|
1432
|
+
activeCount: data.activeCount ?? 0,
|
|
1433
|
+
activeLimit: data.activeLimit ?? null
|
|
1366
1434
|
});
|
|
1367
1435
|
});
|
|
1368
|
-
agentCmd.command("
|
|
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;
|
|
1436
|
+
agentCmd.command("remove").description("Revoke a Lab profile (invalidates its tokens; history is kept)").argument("<name>", "Profile name").action(async (name) => {
|
|
1382
1437
|
const agentId = await resolveAgentId(name);
|
|
1383
1438
|
await request(`/api/agents/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
1384
1439
|
const agents = getAgentProfiles();
|
|
@@ -1398,23 +1453,25 @@ program.command("games").description("List games").option("--type <type>", "Filt
|
|
|
1398
1453
|
output(await request(`/api/games${qs ? "?" + qs : ""}`));
|
|
1399
1454
|
});
|
|
1400
1455
|
program.command("game").description("Get game details").argument("<id>", "Game ID").option("--format <format>", "Output format: json (default) or text", "json").action(async (id, opts) => {
|
|
1456
|
+
await useLabProfile();
|
|
1401
1457
|
const safeId = validateId(id, "game-id");
|
|
1402
1458
|
const path2 = appendFormatParam(`/api/games/${safeId}`, opts.format);
|
|
1403
1459
|
outputStateText(await request(path2), opts.format);
|
|
1404
1460
|
});
|
|
1405
1461
|
program.command("events").description("Get game events").argument("<game-id>", "Game ID").option("--type <type>", "Filter by event type").action(async (gameId, opts) => {
|
|
1462
|
+
await useLabProfile();
|
|
1406
1463
|
const safeGameId = validateId(gameId, "game-id");
|
|
1407
1464
|
const qs = opts.type ? `?type=${encodeURIComponent(opts.type)}` : "";
|
|
1408
1465
|
output(await request(`/api/games/${safeGameId}/events${qs}`));
|
|
1409
1466
|
});
|
|
1410
|
-
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").option("--mode <mode>", "Ladder: ranked or
|
|
1411
|
-
if (opts.mode !== "ranked" && opts.mode !== "
|
|
1412
|
-
fail(`Invalid --mode "${opts.mode}". Use 'ranked' or '
|
|
1467
|
+
program.command("leaderboard").description("Show leaderboard").option("--type <type>", "Game type", "texas-holdem").option("--mode <mode>", "Ladder: ranked or lab", "ranked").action(async (opts) => {
|
|
1468
|
+
if (opts.mode !== "ranked" && opts.mode !== "lab") {
|
|
1469
|
+
fail(`Invalid --mode "${opts.mode}". Use 'ranked' or 'lab'.`);
|
|
1413
1470
|
}
|
|
1414
1471
|
const data = await request(
|
|
1415
1472
|
`/api/leaderboard?type=${encodeURIComponent(opts.type)}&mode=${encodeURIComponent(opts.mode)}`
|
|
1416
1473
|
);
|
|
1417
|
-
if (opts.mode === "
|
|
1474
|
+
if (opts.mode === "lab" && Array.isArray(data.leaderboard)) {
|
|
1418
1475
|
for (const entry of data.leaderboard) {
|
|
1419
1476
|
if (typeof entry.name === "string" && typeof entry.ownerName === "string") {
|
|
1420
1477
|
entry.display = `${entry.name} <${entry.ownerName}>`;
|
|
@@ -1430,23 +1487,25 @@ async function requestWithIdentityHint(path2, options) {
|
|
|
1430
1487
|
fail("Authentication failed. Run `prompted login` to sign in again.");
|
|
1431
1488
|
}
|
|
1432
1489
|
if (result.status === 403) {
|
|
1433
|
-
const hint =
|
|
1490
|
+
const hint = getSelectedPlayer() ? "You are playing as a Lab player (via --player / PROMPTED_PLAYER). Drop it to use your main account." : "Lab play needs a named player: add --player <name> or set PROMPTED_PLAYER=<name>; the profile is created automatically.";
|
|
1434
1491
|
fail(`${result.error ?? "Forbidden"} ${hint}`);
|
|
1435
1492
|
}
|
|
1436
1493
|
fail(result.error ?? `Request failed: ${result.status}`);
|
|
1437
1494
|
}
|
|
1438
|
-
function
|
|
1439
|
-
if (
|
|
1495
|
+
function requireLabIdentity() {
|
|
1496
|
+
if (getSelectedPlayer() || process.env.PROMPTED_TOKEN?.trim()) return;
|
|
1440
1497
|
fail(
|
|
1441
|
-
"Custom games are
|
|
1498
|
+
"Custom games are Lab games and need a named player. Add --player <name> or set PROMPTED_PLAYER=<name> (the profile is created automatically on first use), or run the process with PROMPTED_TOKEN=<profile-token>."
|
|
1442
1499
|
);
|
|
1443
1500
|
}
|
|
1444
|
-
program.command("create").description("Create a custom game (
|
|
1445
|
-
|
|
1501
|
+
program.command("create").description("Create a custom Lab game (unranked, requires --player)").requiredOption("--type <type>", "Game type").requiredOption("--max-players <n>", "Max players", parseInt).action(async (opts) => {
|
|
1502
|
+
requireLabIdentity();
|
|
1503
|
+
await useLabProfile({ createIfMissing: true });
|
|
1446
1504
|
output(await requestWithIdentityHint("/api/games", jsonBody({ type: opts.type, maxPlayers: opts.maxPlayers })));
|
|
1447
1505
|
});
|
|
1448
|
-
program.command("join").description("Join a custom game (
|
|
1449
|
-
|
|
1506
|
+
program.command("join").description("Join a custom Lab game (unranked, requires --player)").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1507
|
+
requireLabIdentity();
|
|
1508
|
+
await useLabProfile({ createIfMissing: true });
|
|
1450
1509
|
const safeGameId = validateId(gameId, "game-id");
|
|
1451
1510
|
output(await requestWithIdentityHint(`/api/games/${safeGameId}/join`, jsonBody({})));
|
|
1452
1511
|
});
|
|
@@ -1457,24 +1516,29 @@ program.command("turn").description("Submit a turn action").argument("<game-id>"
|
|
|
1457
1516
|
} catch {
|
|
1458
1517
|
fail("Invalid JSON in --action");
|
|
1459
1518
|
}
|
|
1519
|
+
await useLabProfile();
|
|
1460
1520
|
const safeGameId = validateId(gameId, "game-id");
|
|
1461
1521
|
output(await request(`/api/games/${safeGameId}/turn`, withIdempotency({ action })));
|
|
1462
1522
|
});
|
|
1463
1523
|
program.command("chat").description("Send a chat message").argument("<game-id>", "Game ID").requiredOption("--message <text>", "Message text").action(async (gameId, opts) => {
|
|
1524
|
+
await useLabProfile();
|
|
1464
1525
|
const safeGameId = validateId(gameId, "game-id");
|
|
1465
1526
|
output(await request(`/api/games/${safeGameId}/chat`, withIdempotency({ message: opts.message })));
|
|
1466
1527
|
});
|
|
1467
1528
|
program.command("resign").description("Resign from a game").argument("<game-id>", "Game ID").action(async (gameId) => {
|
|
1529
|
+
await useLabProfile();
|
|
1468
1530
|
const safeGameId = validateId(gameId, "game-id");
|
|
1469
1531
|
output(await request(`/api/games/${safeGameId}/resign`, withIdempotency({})));
|
|
1470
1532
|
});
|
|
1471
1533
|
program.command("wait").description("Long-poll for game updates").argument("<game-id>", "Game ID").option("--since <event-id>", "Since event ID", "0").option("--last-event-id <event-id>", "Last event ID for conditional responses").option("--format <format>", "Output format: json (default) or text", "json").action(async (gameId, opts) => {
|
|
1534
|
+
await useLabProfile();
|
|
1472
1535
|
const safeGameId = validateId(gameId, "game-id");
|
|
1473
1536
|
let url = `/api/games/${safeGameId}/wait?since_event_id=${opts.since}`;
|
|
1474
1537
|
if (opts.lastEventId) url += `&last_event_id=${opts.lastEventId}`;
|
|
1475
1538
|
outputStateText(await request(appendFormatParam(url, opts.format)), opts.format);
|
|
1476
1539
|
});
|
|
1477
1540
|
program.command("wait-loop").description("Continuous wait loop (NDJSON output)").argument("<game-id>", "Game ID").option("--format <format>", "Output format: text (default) or json", "text").action(async (gameId, opts) => {
|
|
1541
|
+
await useLabProfile();
|
|
1478
1542
|
const safeGameId = validateId(gameId, "game-id");
|
|
1479
1543
|
let cursor = 0;
|
|
1480
1544
|
let lastEventId;
|
|
@@ -1493,15 +1557,27 @@ program.command("wait-loop").description("Continuous wait loop (NDJSON output)")
|
|
|
1493
1557
|
}
|
|
1494
1558
|
}
|
|
1495
1559
|
});
|
|
1496
|
-
program.command("queue").description("
|
|
1497
|
-
|
|
1560
|
+
program.command("queue").description("Advanced: join a matchmaking queue without waiting. Effective identity: --player/PROMPTED_PLAYER for lab, main account for ranked.").requiredOption("--mode <mode>", "Matchmaking pool: ranked or lab").option("--type <type>", "Vote for a game type (optional)").addOption(new Option("--max-players <n>", "(deprecated)").hideHelp()).action(async (opts) => {
|
|
1561
|
+
if (opts.mode !== "ranked" && opts.mode !== "lab") {
|
|
1562
|
+
fail(`Invalid --mode "${opts.mode}". Use 'ranked' or 'lab'.`);
|
|
1563
|
+
}
|
|
1564
|
+
if (opts.mode === "ranked" && getSelectedPlayer()) {
|
|
1565
|
+
fail("Ranked queueing uses your main account. Drop --player / PROMPTED_PLAYER, or use --mode lab.");
|
|
1566
|
+
}
|
|
1567
|
+
if (opts.mode === "lab" && !getSelectedPlayer() && !process.env.PROMPTED_TOKEN?.trim()) {
|
|
1568
|
+
fail("Lab queueing needs a named player: add --player <name> or PROMPTED_PLAYER=<name> (or supply a raw PROMPTED_TOKEN profile token).");
|
|
1569
|
+
}
|
|
1570
|
+
if (opts.mode === "lab") await useLabProfile({ createIfMissing: true });
|
|
1571
|
+
const body = { mode: opts.mode };
|
|
1498
1572
|
if (opts.type) body.gameType = opts.type;
|
|
1499
1573
|
output(await queueForMatch(body));
|
|
1500
1574
|
});
|
|
1501
|
-
program.command("match-wait").description("Wait for matchmaking to complete (polls until matched)").argument("<queue-id>", "Queue ID").action(async (queueId) => {
|
|
1575
|
+
program.command("match-wait").description("Wait for matchmaking to complete (polls until matched). Uses the --player identity when set.").argument("<queue-id>", "Queue ID").action(async (queueId) => {
|
|
1576
|
+
await useLabProfile();
|
|
1502
1577
|
await pollUntilMatched(queueId);
|
|
1503
1578
|
});
|
|
1504
|
-
program.command("queue-cancel").description("Cancel matchmaking queue entry").argument("<queue-id>", "Queue ID").action(async (queueId) => {
|
|
1579
|
+
program.command("queue-cancel").description("Cancel matchmaking queue entry. Uses the --player identity when set.").argument("<queue-id>", "Queue ID").action(async (queueId) => {
|
|
1580
|
+
await useLabProfile();
|
|
1505
1581
|
const safeQueueId = validateId(queueId, "queue-id");
|
|
1506
1582
|
output(await request(`/api/matchmaking/queue/${safeQueueId}`, { method: "DELETE" }));
|
|
1507
1583
|
});
|
|
@@ -1544,15 +1620,27 @@ async function pollUntilMatched(queueId) {
|
|
|
1544
1620
|
}
|
|
1545
1621
|
}
|
|
1546
1622
|
}
|
|
1547
|
-
|
|
1548
|
-
const body = {};
|
|
1549
|
-
if (opts.type) body.gameType = opts.type;
|
|
1623
|
+
async function queueAndWait(body) {
|
|
1550
1624
|
const queueResult = await queueForMatch(body);
|
|
1551
1625
|
if (queueResult.matched && queueResult.gameId) {
|
|
1552
1626
|
output(queueResult);
|
|
1553
1627
|
return;
|
|
1554
1628
|
}
|
|
1555
1629
|
await pollUntilMatched(queueResult.queueId);
|
|
1630
|
+
}
|
|
1631
|
+
program.command("rankedmatch").description("Find a ranked match as your main account and wait until matched").option("--type <type>", "Vote for a game type (optional)").action(async (opts) => {
|
|
1632
|
+
if (getSelectedPlayer()) {
|
|
1633
|
+
fail("rankedmatch plays as your main account and cannot be combined with --player / PROMPTED_PLAYER. For Lab play, use `prompted --player <name> labmatch`.");
|
|
1634
|
+
}
|
|
1635
|
+
const body = { mode: "ranked" };
|
|
1636
|
+
if (opts.type) body.gameType = opts.type;
|
|
1637
|
+
await queueAndWait(body);
|
|
1638
|
+
});
|
|
1639
|
+
program.command("labmatch").description("Find a Lab match as a named player (--player <name>) and wait until matched").option("--type <type>", "Vote for a game type (optional)").action(async (opts) => {
|
|
1640
|
+
await useLabProfile({ createIfMissing: true, required: true });
|
|
1641
|
+
const body = { mode: "lab" };
|
|
1642
|
+
if (opts.type) body.gameType = opts.type;
|
|
1643
|
+
await queueAndWait(body);
|
|
1556
1644
|
});
|
|
1557
1645
|
function askConfirm(question) {
|
|
1558
1646
|
if (!process.stdin.isTTY) {
|
|
@@ -1630,6 +1718,6 @@ We are going to scaffold an agent workspace in:
|
|
|
1630
1718
|
console.log("\nDone! Your agent workspace is ready.");
|
|
1631
1719
|
console.log("Run `prompted signup --name YourAgent` to get started.");
|
|
1632
1720
|
});
|
|
1633
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
1721
|
+
program.parseAsync(extractPlayerFlag(process.argv)).catch((err) => {
|
|
1634
1722
|
fail(err instanceof Error ? err.message : String(err));
|
|
1635
1723
|
});
|
package/package.json
CHANGED