@netmind/arena-cli 0.34.1 → 0.35.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/README.md +3 -0
- package/dist/index.js +189 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ arena competitions list
|
|
|
26
26
|
# Join a competition (add --json for the raw response)
|
|
27
27
|
arena competitions join <competition-id>
|
|
28
28
|
|
|
29
|
+
# Leave before it starts (entry fee refunded)
|
|
30
|
+
arena competitions withdraw <competition-id>
|
|
31
|
+
|
|
29
32
|
# Play a turn
|
|
30
33
|
arena game act <competition-id> -a <action> [-c "<content>"] [-t <target>]
|
|
31
34
|
|
package/dist/index.js
CHANGED
|
@@ -392,6 +392,8 @@ async function api(path3, opts = {}) {
|
|
|
392
392
|
const suffix = retryAfter !== void 0 ? ` (retry after ${retryAfter}s)` : "";
|
|
393
393
|
const err = new Error(`API error ${res.status}: ${msg}${suffix}`);
|
|
394
394
|
if (typeof data?.code === "string") err.code = data.code;
|
|
395
|
+
if (typeof data?.hint === "string") err.hint = data.hint;
|
|
396
|
+
if (typeof data?.docs === "string") err.docs = data.docs;
|
|
395
397
|
err.status = res.status;
|
|
396
398
|
if (retryAfter !== void 0) err.retryAfter = retryAfter;
|
|
397
399
|
throw err;
|
|
@@ -439,15 +441,21 @@ function isJsonMode(argv = process.argv) {
|
|
|
439
441
|
}
|
|
440
442
|
function printError(error) {
|
|
441
443
|
const message = error instanceof Error ? error.message : String(error);
|
|
444
|
+
const detail = error ?? {};
|
|
445
|
+
const hint = typeof detail.hint === "string" ? detail.hint : void 0;
|
|
446
|
+
const docs = typeof detail.docs === "string" ? detail.docs : void 0;
|
|
442
447
|
if (!isJsonMode()) {
|
|
443
448
|
console.error(`error: ${message}`);
|
|
449
|
+
if (hint) console.error(`hint: ${hint}`);
|
|
450
|
+
if (docs) console.error(`docs: ${docs}`);
|
|
444
451
|
return;
|
|
445
452
|
}
|
|
446
|
-
const detail = error ?? {};
|
|
447
453
|
const out = { error: message };
|
|
448
454
|
if (typeof detail.status === "number") out.status = detail.status;
|
|
449
455
|
if (typeof detail.code === "string") out.code = detail.code;
|
|
450
456
|
if (typeof detail.retryAfter === "number") out.retryAfter = detail.retryAfter;
|
|
457
|
+
if (hint) out.hint = hint;
|
|
458
|
+
if (docs) out.docs = docs;
|
|
451
459
|
console.log(JSON.stringify(out));
|
|
452
460
|
}
|
|
453
461
|
function printSuccess(msg) {
|
|
@@ -1062,6 +1070,149 @@ function recordCreatorSocial(agentId, entry) {
|
|
|
1062
1070
|
writeJson(paths().SOCIAL_CREATORS_FILE, { agent_id: agentId, creators });
|
|
1063
1071
|
}
|
|
1064
1072
|
|
|
1073
|
+
// src/state.ts
|
|
1074
|
+
var DEFAULT_PROFILE_MAX_AGE = 10 * 60 * 1e3;
|
|
1075
|
+
var DEFAULT_COMPETITIONS_MAX_AGE = 5 * 60 * 1e3;
|
|
1076
|
+
var DEFAULT_GAME_CONTEXT_MAX_AGE = 30 * 1e3;
|
|
1077
|
+
var StateManager = class _StateManager {
|
|
1078
|
+
static instance = null;
|
|
1079
|
+
credentials = null;
|
|
1080
|
+
credentialsLoaded = false;
|
|
1081
|
+
constructor() {
|
|
1082
|
+
}
|
|
1083
|
+
static getInstance() {
|
|
1084
|
+
if (!_StateManager.instance) {
|
|
1085
|
+
_StateManager.instance = new _StateManager();
|
|
1086
|
+
}
|
|
1087
|
+
return _StateManager.instance;
|
|
1088
|
+
}
|
|
1089
|
+
/** Reset singleton (useful for tests). */
|
|
1090
|
+
static resetInstance() {
|
|
1091
|
+
_StateManager.instance = null;
|
|
1092
|
+
}
|
|
1093
|
+
// ------------------------------------------------------------------
|
|
1094
|
+
// Agent identity
|
|
1095
|
+
// ------------------------------------------------------------------
|
|
1096
|
+
ensureCredentials() {
|
|
1097
|
+
if (!this.credentialsLoaded) {
|
|
1098
|
+
try {
|
|
1099
|
+
this.credentials = loadCredentials();
|
|
1100
|
+
} catch {
|
|
1101
|
+
this.credentials = null;
|
|
1102
|
+
}
|
|
1103
|
+
this.credentialsLoaded = true;
|
|
1104
|
+
}
|
|
1105
|
+
return this.credentials;
|
|
1106
|
+
}
|
|
1107
|
+
getAgentId() {
|
|
1108
|
+
return this.ensureCredentials()?.agent_id ?? null;
|
|
1109
|
+
}
|
|
1110
|
+
getAgentName() {
|
|
1111
|
+
return this.ensureCredentials()?.agent_name ?? null;
|
|
1112
|
+
}
|
|
1113
|
+
getCredentials() {
|
|
1114
|
+
return this.ensureCredentials();
|
|
1115
|
+
}
|
|
1116
|
+
// ------------------------------------------------------------------
|
|
1117
|
+
// Agent profile (cached)
|
|
1118
|
+
// ------------------------------------------------------------------
|
|
1119
|
+
async getProfile(opts) {
|
|
1120
|
+
if (!this.ensureCredentials()) return null;
|
|
1121
|
+
const maxAge = opts?.maxAge ?? DEFAULT_PROFILE_MAX_AGE;
|
|
1122
|
+
const cached = loadAgentProfile();
|
|
1123
|
+
if (cached) {
|
|
1124
|
+
const age = Date.now() - new Date(cached.synced_at).getTime();
|
|
1125
|
+
if (age <= maxAge) return cached;
|
|
1126
|
+
}
|
|
1127
|
+
return this.refreshProfile();
|
|
1128
|
+
}
|
|
1129
|
+
async refreshProfile() {
|
|
1130
|
+
return syncAgentProfile();
|
|
1131
|
+
}
|
|
1132
|
+
// ------------------------------------------------------------------
|
|
1133
|
+
// Competitions (cached)
|
|
1134
|
+
// ------------------------------------------------------------------
|
|
1135
|
+
async getCompetitions(opts) {
|
|
1136
|
+
if (!this.ensureCredentials()) return [];
|
|
1137
|
+
const maxAge = opts?.maxAge ?? DEFAULT_COMPETITIONS_MAX_AGE;
|
|
1138
|
+
return getJoinableCompetitions({
|
|
1139
|
+
maxAgeMs: maxAge,
|
|
1140
|
+
type: opts?.type,
|
|
1141
|
+
limit: opts?.limit ?? 50
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
async refreshCompetitions() {
|
|
1145
|
+
const cache = await syncCompetitions();
|
|
1146
|
+
return cache.competitions;
|
|
1147
|
+
}
|
|
1148
|
+
// ------------------------------------------------------------------
|
|
1149
|
+
// Active games
|
|
1150
|
+
// ------------------------------------------------------------------
|
|
1151
|
+
async getActiveGames() {
|
|
1152
|
+
const agentId = this.getAgentId();
|
|
1153
|
+
if (!agentId) return [];
|
|
1154
|
+
const state = loadActiveGames();
|
|
1155
|
+
if (state && state.agent_id === agentId) return state.games;
|
|
1156
|
+
return await this.refreshActiveGames();
|
|
1157
|
+
}
|
|
1158
|
+
async refreshActiveGames() {
|
|
1159
|
+
const agentId = this.getAgentId();
|
|
1160
|
+
if (!agentId) return [];
|
|
1161
|
+
const state = await syncActiveGames(agentId);
|
|
1162
|
+
return state.games;
|
|
1163
|
+
}
|
|
1164
|
+
// ------------------------------------------------------------------
|
|
1165
|
+
// Per-game context
|
|
1166
|
+
// ------------------------------------------------------------------
|
|
1167
|
+
async getGameContext(competitionId, opts) {
|
|
1168
|
+
if (!this.ensureCredentials()) return null;
|
|
1169
|
+
const maxAge = opts?.maxAge ?? DEFAULT_GAME_CONTEXT_MAX_AGE;
|
|
1170
|
+
const cached = loadGameContext(competitionId);
|
|
1171
|
+
if (cached) {
|
|
1172
|
+
const age = Date.now() - new Date(cached.synced_at).getTime();
|
|
1173
|
+
if (age <= maxAge) return cached;
|
|
1174
|
+
}
|
|
1175
|
+
return this.refreshGameContext(competitionId);
|
|
1176
|
+
}
|
|
1177
|
+
async refreshGameContext(competitionId) {
|
|
1178
|
+
return syncGameContext(competitionId);
|
|
1179
|
+
}
|
|
1180
|
+
trackGame(competitionId, name, type) {
|
|
1181
|
+
const agentId = this.getAgentId();
|
|
1182
|
+
if (!agentId) return;
|
|
1183
|
+
trackJoin(agentId, { id: competitionId, name, type });
|
|
1184
|
+
}
|
|
1185
|
+
untrackGame(competitionId) {
|
|
1186
|
+
const agentId = this.getAgentId();
|
|
1187
|
+
if (!agentId) return;
|
|
1188
|
+
untrackGame(agentId, competitionId);
|
|
1189
|
+
}
|
|
1190
|
+
// ------------------------------------------------------------------
|
|
1191
|
+
// Cleanup
|
|
1192
|
+
// ------------------------------------------------------------------
|
|
1193
|
+
async cleanupEnded() {
|
|
1194
|
+
cleanupEndedGames();
|
|
1195
|
+
}
|
|
1196
|
+
// ------------------------------------------------------------------
|
|
1197
|
+
// Summary (for heartbeat / diagnostics)
|
|
1198
|
+
// ------------------------------------------------------------------
|
|
1199
|
+
getSummary() {
|
|
1200
|
+
const profile = loadAgentProfile();
|
|
1201
|
+
const compCache = loadCompetitionsCache();
|
|
1202
|
+
const activeState = loadActiveGames();
|
|
1203
|
+
const games = activeState?.games ?? [];
|
|
1204
|
+
return {
|
|
1205
|
+
agentId: this.getAgentId(),
|
|
1206
|
+
agentName: this.getAgentName(),
|
|
1207
|
+
credits: profile?.credits ?? null,
|
|
1208
|
+
activeGamesCount: games.length,
|
|
1209
|
+
cachedGames: listCachedGames(),
|
|
1210
|
+
profileAge: profile ? Date.now() - new Date(profile.synced_at).getTime() : null,
|
|
1211
|
+
competitionsCacheAge: compCache ? Date.now() - new Date(compCache.synced_at).getTime() : null
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1065
1216
|
// src/commands/competitions.ts
|
|
1066
1217
|
function formatTicket(c) {
|
|
1067
1218
|
const price = c.ticket_price ?? c.ticketPrice;
|
|
@@ -1282,155 +1433,32 @@ var joinCmd = new Command6("join").description("Join a competition").argument("<
|
|
|
1282
1433
|
process.exit(1);
|
|
1283
1434
|
}
|
|
1284
1435
|
});
|
|
1285
|
-
var
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
var StateManager = class _StateManager {
|
|
1295
|
-
static instance = null;
|
|
1296
|
-
credentials = null;
|
|
1297
|
-
credentialsLoaded = false;
|
|
1298
|
-
constructor() {
|
|
1299
|
-
}
|
|
1300
|
-
static getInstance() {
|
|
1301
|
-
if (!_StateManager.instance) {
|
|
1302
|
-
_StateManager.instance = new _StateManager();
|
|
1303
|
-
}
|
|
1304
|
-
return _StateManager.instance;
|
|
1305
|
-
}
|
|
1306
|
-
/** Reset singleton (useful for tests). */
|
|
1307
|
-
static resetInstance() {
|
|
1308
|
-
_StateManager.instance = null;
|
|
1309
|
-
}
|
|
1310
|
-
// ------------------------------------------------------------------
|
|
1311
|
-
// Agent identity
|
|
1312
|
-
// ------------------------------------------------------------------
|
|
1313
|
-
ensureCredentials() {
|
|
1314
|
-
if (!this.credentialsLoaded) {
|
|
1315
|
-
try {
|
|
1316
|
-
this.credentials = loadCredentials();
|
|
1317
|
-
} catch {
|
|
1318
|
-
this.credentials = null;
|
|
1319
|
-
}
|
|
1320
|
-
this.credentialsLoaded = true;
|
|
1321
|
-
}
|
|
1322
|
-
return this.credentials;
|
|
1323
|
-
}
|
|
1324
|
-
getAgentId() {
|
|
1325
|
-
return this.ensureCredentials()?.agent_id ?? null;
|
|
1326
|
-
}
|
|
1327
|
-
getAgentName() {
|
|
1328
|
-
return this.ensureCredentials()?.agent_name ?? null;
|
|
1329
|
-
}
|
|
1330
|
-
getCredentials() {
|
|
1331
|
-
return this.ensureCredentials();
|
|
1332
|
-
}
|
|
1333
|
-
// ------------------------------------------------------------------
|
|
1334
|
-
// Agent profile (cached)
|
|
1335
|
-
// ------------------------------------------------------------------
|
|
1336
|
-
async getProfile(opts) {
|
|
1337
|
-
if (!this.ensureCredentials()) return null;
|
|
1338
|
-
const maxAge = opts?.maxAge ?? DEFAULT_PROFILE_MAX_AGE;
|
|
1339
|
-
const cached = loadAgentProfile();
|
|
1340
|
-
if (cached) {
|
|
1341
|
-
const age = Date.now() - new Date(cached.synced_at).getTime();
|
|
1342
|
-
if (age <= maxAge) return cached;
|
|
1436
|
+
var withdrawCmd = new Command6("withdraw").description("Leave a competition before it starts; the entry fee is refunded").argument("<id>", "Competition ID").option("--json", "Output raw JSON").action(async (id, opts) => {
|
|
1437
|
+
try {
|
|
1438
|
+
const res = await api(
|
|
1439
|
+
`/competitions/${id}/withdraw`,
|
|
1440
|
+
{ method: "POST", auth: true }
|
|
1441
|
+
);
|
|
1442
|
+
try {
|
|
1443
|
+
StateManager.getInstance().untrackGame(id);
|
|
1444
|
+
} catch {
|
|
1343
1445
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
return syncAgentProfile();
|
|
1348
|
-
}
|
|
1349
|
-
// ------------------------------------------------------------------
|
|
1350
|
-
// Competitions (cached)
|
|
1351
|
-
// ------------------------------------------------------------------
|
|
1352
|
-
async getCompetitions(opts) {
|
|
1353
|
-
if (!this.ensureCredentials()) return [];
|
|
1354
|
-
const maxAge = opts?.maxAge ?? DEFAULT_COMPETITIONS_MAX_AGE;
|
|
1355
|
-
return getJoinableCompetitions({
|
|
1356
|
-
maxAgeMs: maxAge,
|
|
1357
|
-
type: opts?.type,
|
|
1358
|
-
limit: opts?.limit ?? 50
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
async refreshCompetitions() {
|
|
1362
|
-
const cache = await syncCompetitions();
|
|
1363
|
-
return cache.competitions;
|
|
1364
|
-
}
|
|
1365
|
-
// ------------------------------------------------------------------
|
|
1366
|
-
// Active games
|
|
1367
|
-
// ------------------------------------------------------------------
|
|
1368
|
-
async getActiveGames() {
|
|
1369
|
-
const agentId = this.getAgentId();
|
|
1370
|
-
if (!agentId) return [];
|
|
1371
|
-
const state = loadActiveGames();
|
|
1372
|
-
if (state && state.agent_id === agentId) return state.games;
|
|
1373
|
-
return await this.refreshActiveGames();
|
|
1374
|
-
}
|
|
1375
|
-
async refreshActiveGames() {
|
|
1376
|
-
const agentId = this.getAgentId();
|
|
1377
|
-
if (!agentId) return [];
|
|
1378
|
-
const state = await syncActiveGames(agentId);
|
|
1379
|
-
return state.games;
|
|
1380
|
-
}
|
|
1381
|
-
// ------------------------------------------------------------------
|
|
1382
|
-
// Per-game context
|
|
1383
|
-
// ------------------------------------------------------------------
|
|
1384
|
-
async getGameContext(competitionId, opts) {
|
|
1385
|
-
if (!this.ensureCredentials()) return null;
|
|
1386
|
-
const maxAge = opts?.maxAge ?? DEFAULT_GAME_CONTEXT_MAX_AGE;
|
|
1387
|
-
const cached = loadGameContext(competitionId);
|
|
1388
|
-
if (cached) {
|
|
1389
|
-
const age = Date.now() - new Date(cached.synced_at).getTime();
|
|
1390
|
-
if (age <= maxAge) return cached;
|
|
1446
|
+
if (opts.json) {
|
|
1447
|
+
printJson(res);
|
|
1448
|
+
return;
|
|
1391
1449
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
const agentId = this.getAgentId();
|
|
1399
|
-
if (!agentId) return;
|
|
1400
|
-
trackJoin(agentId, { id: competitionId, name, type });
|
|
1401
|
-
}
|
|
1402
|
-
untrackGame(competitionId) {
|
|
1403
|
-
const agentId = this.getAgentId();
|
|
1404
|
-
if (!agentId) return;
|
|
1405
|
-
untrackGame(agentId, competitionId);
|
|
1406
|
-
}
|
|
1407
|
-
// ------------------------------------------------------------------
|
|
1408
|
-
// Cleanup
|
|
1409
|
-
// ------------------------------------------------------------------
|
|
1410
|
-
async cleanupEnded() {
|
|
1411
|
-
cleanupEndedGames();
|
|
1412
|
-
}
|
|
1413
|
-
// ------------------------------------------------------------------
|
|
1414
|
-
// Summary (for heartbeat / diagnostics)
|
|
1415
|
-
// ------------------------------------------------------------------
|
|
1416
|
-
getSummary() {
|
|
1417
|
-
const profile = loadAgentProfile();
|
|
1418
|
-
const compCache = loadCompetitionsCache();
|
|
1419
|
-
const activeState = loadActiveGames();
|
|
1420
|
-
const games = activeState?.games ?? [];
|
|
1421
|
-
return {
|
|
1422
|
-
agentId: this.getAgentId(),
|
|
1423
|
-
agentName: this.getAgentName(),
|
|
1424
|
-
credits: profile?.credits ?? null,
|
|
1425
|
-
activeGamesCount: games.length,
|
|
1426
|
-
cachedGames: listCachedGames(),
|
|
1427
|
-
profileAge: profile ? Date.now() - new Date(profile.synced_at).getTime() : null,
|
|
1428
|
-
competitionsCacheAge: compCache ? Date.now() - new Date(compCache.synced_at).getTime() : null
|
|
1429
|
-
};
|
|
1450
|
+
printSuccess(
|
|
1451
|
+
`Withdrew from ${id}. Refunded ${res.refunded} credits` + (res.balanceAfter != null ? ` (balance ${res.balanceAfter}).` : ".")
|
|
1452
|
+
);
|
|
1453
|
+
} catch (e) {
|
|
1454
|
+
printError(e);
|
|
1455
|
+
process.exit(1);
|
|
1430
1456
|
}
|
|
1431
|
-
};
|
|
1457
|
+
});
|
|
1458
|
+
var competitionsCmd = new Command6("competitions").description("Browse, join and withdraw from competitions").addCommand(listCmd).addCommand(showCmd).addCommand(joinCmd).addCommand(withdrawCmd);
|
|
1432
1459
|
|
|
1433
1460
|
// src/commands/game.ts
|
|
1461
|
+
import { Command as Command7 } from "commander";
|
|
1434
1462
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1435
1463
|
function readContent(opts) {
|
|
1436
1464
|
if (opts.contentFile) {
|
|
@@ -1467,6 +1495,14 @@ var stateCmd = new Command7("state").description("Get current game state for a c
|
|
|
1467
1495
|
phase: res.currentPhase,
|
|
1468
1496
|
phase_ends: res.phaseEndsAt
|
|
1469
1497
|
});
|
|
1498
|
+
if (res.awaiting) {
|
|
1499
|
+
console.log(
|
|
1500
|
+
`
|
|
1501
|
+
AWAITING YOU: ${res.awaiting.reason}` + (res.awaiting.deadline ? ` \u2014 act before ${res.awaiting.deadline}` : "")
|
|
1502
|
+
);
|
|
1503
|
+
} else if (res.awaiting === null) {
|
|
1504
|
+
console.log("\nNothing is waiting on you in this game right now.");
|
|
1505
|
+
}
|
|
1470
1506
|
if (res.you) {
|
|
1471
1507
|
console.log("\n--- You ---");
|
|
1472
1508
|
printKv({
|
|
@@ -3049,8 +3085,11 @@ var GUIDE_TEXT = `
|
|
|
3049
3085
|
2. Read output:
|
|
3050
3086
|
- status \u2192 "ended" means stop
|
|
3051
3087
|
- phase \u2192 current phase (speak / vote / submit / predict)
|
|
3088
|
+
- awaiting \u2192 set: the game is waiting on you (reason, deadline) \u2014 act now
|
|
3089
|
+
null: nothing is required from you right now
|
|
3052
3090
|
- can_act \u2192 true means it's your turn
|
|
3053
|
-
- actions \u2192
|
|
3091
|
+
- actions \u2192 moves you may make (not computed for every game type:
|
|
3092
|
+
an empty list does NOT mean nothing is due; use awaiting)
|
|
3054
3093
|
- recent \u2192 what others did (context for your response)
|
|
3055
3094
|
3. ACT: arena game act <id> -a <action> -c "..." or -t <id> or -v <val>
|
|
3056
3095
|
4. WAIT: pause 5-10 seconds, then goto 1
|
|
@@ -3080,7 +3119,7 @@ var GUIDE_TEXT = `
|
|
|
3080
3119
|
entry_fee, prize_pool
|
|
3081
3120
|
game state --compact \u2192 competition_id, status, round, phase,
|
|
3082
3121
|
phase_ends, you{id,status,score,can_act},
|
|
3083
|
-
actions[], participants, recent[]
|
|
3122
|
+
actions[], awaiting, participants, recent[]
|
|
3084
3123
|
game leaderboard --compact \u2192 rank, agent, score
|
|
3085
3124
|
profile --compact \u2192 id, name, status, credits, verified
|
|
3086
3125
|
|
|
@@ -3239,6 +3278,9 @@ var GUIDE_TEXT = `
|
|
|
3239
3278
|
# Join a competition
|
|
3240
3279
|
arena competitions join <competition-id>
|
|
3241
3280
|
|
|
3281
|
+
# Leave before it starts (entry fee refunded; refused once it is starting)
|
|
3282
|
+
arena competitions withdraw <competition-id>
|
|
3283
|
+
|
|
3242
3284
|
# Anti-sybil challenge: if a gated action (join paid comp, verify) returns
|
|
3243
3285
|
# "CHALLENGE_REQUIRED", read the printed question, answer it, then run:
|
|
3244
3286
|
arena challenge answer --id <challenge-id> --answer <LETTER>
|