clawtan 0.2.4 → 0.2.6
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/clawtan/cli.py +251 -25
- package/package.json +1 -1
package/clawtan/cli.py
CHANGED
|
@@ -7,6 +7,7 @@ by LLM agents. Session credentials are saved automatically on join.
|
|
|
7
7
|
|
|
8
8
|
Typical agent flow:
|
|
9
9
|
clawtan quick-join --name "LobsterBot" # session saved to ~/.clawtan_session
|
|
10
|
+
clawtan whoami # verify correct session
|
|
10
11
|
clawtan board # once, to learn the map
|
|
11
12
|
clawtan wait # blocks until your turn
|
|
12
13
|
clawtan act ROLL_THE_SHELLS
|
|
@@ -14,6 +15,10 @@ Typical agent flow:
|
|
|
14
15
|
clawtan act END_TIDE
|
|
15
16
|
clawtan wait # next turn...
|
|
16
17
|
|
|
18
|
+
Between games:
|
|
19
|
+
clawtan clear-session # remove default session
|
|
20
|
+
clawtan clear-session --all # remove ALL saved sessions
|
|
21
|
+
|
|
17
22
|
Multi-player on one machine (each terminal):
|
|
18
23
|
clawtan join <GAME_ID> # join, note your assigned color
|
|
19
24
|
export CLAWTAN_COLOR=<COLOR> # lock this terminal to your player
|
|
@@ -152,7 +157,22 @@ def _save_session(game_id: str, token: str, color: str):
|
|
|
152
157
|
json.dump(data, f)
|
|
153
158
|
except OSError as e:
|
|
154
159
|
print(f"Warning: could not save session to {path}: {e}", file=sys.stderr)
|
|
160
|
+
|
|
161
|
+
# Warn if the default session pointed at a different game (stale session)
|
|
155
162
|
default = os.environ.get("CLAWTAN_SESSION_FILE") or os.path.expanduser("~/.clawtan_session")
|
|
163
|
+
try:
|
|
164
|
+
with open(default) as f:
|
|
165
|
+
prev = json.load(f)
|
|
166
|
+
prev_game = prev.get("GAME")
|
|
167
|
+
if prev_game and prev_game != game_id:
|
|
168
|
+
print(
|
|
169
|
+
f" Note: overwriting previous session (game {prev_game})."
|
|
170
|
+
f" Run 'clawtan clear-session --game {prev_game}' to clean up old files.",
|
|
171
|
+
file=sys.stderr,
|
|
172
|
+
)
|
|
173
|
+
except (OSError, json.JSONDecodeError):
|
|
174
|
+
pass
|
|
175
|
+
|
|
156
176
|
try:
|
|
157
177
|
with open(default, "w") as f:
|
|
158
178
|
json.dump(data, f)
|
|
@@ -179,24 +199,35 @@ def _find_session(game_hint: str | None = None, color_hint: str | None = None) -
|
|
|
179
199
|
pass
|
|
180
200
|
|
|
181
201
|
if (game_hint or color_hint) and os.path.isdir(_SESSIONS_DIR):
|
|
182
|
-
matches = []
|
|
202
|
+
matches = [] # list of (mtime, data) tuples
|
|
183
203
|
for fname in os.listdir(_SESSIONS_DIR):
|
|
184
204
|
if not fname.endswith(".json"):
|
|
185
205
|
continue
|
|
206
|
+
fpath = os.path.join(_SESSIONS_DIR, fname)
|
|
186
207
|
try:
|
|
187
|
-
with open(
|
|
208
|
+
with open(fpath) as f:
|
|
188
209
|
data = json.load(f)
|
|
189
210
|
if game_hint and data.get("GAME") != game_hint:
|
|
190
211
|
continue
|
|
191
212
|
if color_hint and data.get("COLOR") != color_hint:
|
|
192
213
|
continue
|
|
193
|
-
|
|
214
|
+
mtime = os.path.getmtime(fpath)
|
|
215
|
+
matches.append((mtime, data))
|
|
194
216
|
except (OSError, json.JSONDecodeError):
|
|
195
217
|
continue
|
|
196
|
-
if len(matches) == 1:
|
|
197
|
-
return matches[0]
|
|
198
218
|
if matches:
|
|
199
|
-
|
|
219
|
+
# Sort by mtime descending so most recently written session wins
|
|
220
|
+
matches.sort(key=lambda m: m[0], reverse=True)
|
|
221
|
+
if len(matches) > 1:
|
|
222
|
+
chosen = matches[0][1]
|
|
223
|
+
print(
|
|
224
|
+
f"Warning: {len(matches)} session files match hints"
|
|
225
|
+
f" (game={game_hint}, color={color_hint})."
|
|
226
|
+
f" Using most recent: {chosen.get('GAME')}_{chosen.get('COLOR')}."
|
|
227
|
+
f" Pass --game and --color to be explicit.",
|
|
228
|
+
file=sys.stderr,
|
|
229
|
+
)
|
|
230
|
+
return matches[0][1]
|
|
200
231
|
|
|
201
232
|
default = os.path.expanduser("~/.clawtan_session")
|
|
202
233
|
try:
|
|
@@ -240,6 +271,89 @@ def _require(name: str, val):
|
|
|
240
271
|
return val
|
|
241
272
|
|
|
242
273
|
|
|
274
|
+
def cmd_whoami(args):
|
|
275
|
+
"""Show the currently resolved session (game, color, token)."""
|
|
276
|
+
game, token, color = _resolve_session(
|
|
277
|
+
getattr(args, "game", None),
|
|
278
|
+
getattr(args, "token", None),
|
|
279
|
+
getattr(args, "color", None),
|
|
280
|
+
)
|
|
281
|
+
_header("CURRENT SESSION")
|
|
282
|
+
print(f" Game: {game or '(not set)'}")
|
|
283
|
+
print(f" Color: {color or '(not set)'}")
|
|
284
|
+
print(f" Token: {token[:12] + '...' if token and len(token) > 12 else token or '(not set)'}")
|
|
285
|
+
|
|
286
|
+
# Show where each value came from
|
|
287
|
+
sources = []
|
|
288
|
+
if getattr(args, "game", None) or getattr(args, "color", None):
|
|
289
|
+
sources.append("CLI flags")
|
|
290
|
+
for var in ("CLAWTAN_GAME", "CLAWTAN_TOKEN", "CLAWTAN_COLOR"):
|
|
291
|
+
if os.environ.get(var):
|
|
292
|
+
sources.append(f"env ${var}")
|
|
293
|
+
if not sources:
|
|
294
|
+
sources.append("session file")
|
|
295
|
+
print(f" Source: {', '.join(sources)}")
|
|
296
|
+
|
|
297
|
+
if not (game and token and color):
|
|
298
|
+
print(
|
|
299
|
+
"\n Session incomplete. Run 'clawtan quick-join' or pass --game/--token/--color.",
|
|
300
|
+
file=sys.stderr,
|
|
301
|
+
)
|
|
302
|
+
sys.exit(1)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def cmd_clear_session(args):
|
|
306
|
+
"""Remove saved session files."""
|
|
307
|
+
removed = 0
|
|
308
|
+
|
|
309
|
+
if args.all:
|
|
310
|
+
# Remove all session files and the default
|
|
311
|
+
if os.path.isdir(_SESSIONS_DIR):
|
|
312
|
+
for fname in os.listdir(_SESSIONS_DIR):
|
|
313
|
+
if fname.endswith(".json"):
|
|
314
|
+
os.remove(os.path.join(_SESSIONS_DIR, fname))
|
|
315
|
+
removed += 1
|
|
316
|
+
default = os.path.expanduser("~/.clawtan_session")
|
|
317
|
+
if os.path.exists(default):
|
|
318
|
+
os.remove(default)
|
|
319
|
+
removed += 1
|
|
320
|
+
print(f"Cleared {removed} session file(s).")
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
# Clear a specific game, or just the default session
|
|
324
|
+
game_hint = args.game
|
|
325
|
+
color_hint = args.color
|
|
326
|
+
|
|
327
|
+
if game_hint or color_hint:
|
|
328
|
+
if os.path.isdir(_SESSIONS_DIR):
|
|
329
|
+
for fname in os.listdir(_SESSIONS_DIR):
|
|
330
|
+
if not fname.endswith(".json"):
|
|
331
|
+
continue
|
|
332
|
+
fpath = os.path.join(_SESSIONS_DIR, fname)
|
|
333
|
+
try:
|
|
334
|
+
with open(fpath) as f:
|
|
335
|
+
data = json.load(f)
|
|
336
|
+
if game_hint and data.get("GAME") != game_hint:
|
|
337
|
+
continue
|
|
338
|
+
if color_hint and data.get("COLOR") != color_hint:
|
|
339
|
+
continue
|
|
340
|
+
os.remove(fpath)
|
|
341
|
+
removed += 1
|
|
342
|
+
except (OSError, json.JSONDecodeError):
|
|
343
|
+
continue
|
|
344
|
+
else:
|
|
345
|
+
# No hints — clear the default session file
|
|
346
|
+
default = os.path.expanduser("~/.clawtan_session")
|
|
347
|
+
if os.path.exists(default):
|
|
348
|
+
os.remove(default)
|
|
349
|
+
removed += 1
|
|
350
|
+
|
|
351
|
+
if removed:
|
|
352
|
+
print(f"Cleared {removed} session file(s).")
|
|
353
|
+
else:
|
|
354
|
+
print("No matching session files found.")
|
|
355
|
+
|
|
356
|
+
|
|
243
357
|
# ---------------------------------------------------------------------------
|
|
244
358
|
# State extraction (operates on a full game-state dict)
|
|
245
359
|
# ---------------------------------------------------------------------------
|
|
@@ -391,7 +505,11 @@ def _print_opponents(opponents: list):
|
|
|
391
505
|
|
|
392
506
|
|
|
393
507
|
def _format_trade_tuple(val: list) -> str:
|
|
394
|
-
"""Decode
|
|
508
|
+
"""Decode trade resources (first 10 elements) into 'give X for Y' text.
|
|
509
|
+
|
|
510
|
+
Works for 10-element (OFFER_TRADE) and 11-element (current_trade,
|
|
511
|
+
ACCEPT/REJECT/CONFIRM) tuples — extra elements are ignored.
|
|
512
|
+
"""
|
|
395
513
|
giving = {RESOURCES[i]: val[i] for i in range(5) if val[i]}
|
|
396
514
|
wanting = {RESOURCES[i]: val[i + 5] for i in range(5) if val[i + 5]}
|
|
397
515
|
give_str = ", ".join(f"{n}x {r}" for r, n in giving.items()) or "nothing"
|
|
@@ -424,16 +542,20 @@ _ACTION_HINTS = {
|
|
|
424
542
|
" CLI: clawtan act OFFER_TRADE '[0,0,0,1,0,0,1,0,0,0]' # give 1 KELP, want 1 CORAL"
|
|
425
543
|
),
|
|
426
544
|
"ACCEPT_TRADE": (
|
|
427
|
-
"Accept a trade offer. Value =
|
|
428
|
-
"
|
|
545
|
+
"Accept a trade offer. Value = 11-element current_trade tuple\n"
|
|
546
|
+
" (10 resource ints + offerer's turn index, echoed from the offer).\n"
|
|
547
|
+
" Just use the value shown in your available actions.\n"
|
|
548
|
+
" CLI: clawtan act ACCEPT_TRADE '[0,0,0,1,0,0,1,0,0,0,0]'"
|
|
429
549
|
),
|
|
430
550
|
"REJECT_TRADE": (
|
|
431
|
-
"Reject a trade offer. Value =
|
|
432
|
-
"
|
|
551
|
+
"Reject a trade offer. Value = 11-element current_trade tuple\n"
|
|
552
|
+
" (10 resource ints + offerer's turn index, echoed from the offer).\n"
|
|
553
|
+
" Just use the value shown in your available actions.\n"
|
|
554
|
+
" CLI: clawtan act REJECT_TRADE '[0,0,0,1,0,0,1,0,0,0,0]'"
|
|
433
555
|
),
|
|
434
556
|
"CONFIRM_TRADE": (
|
|
435
557
|
"Confirm trade with a specific acceptee. Value = 11-element list:\n"
|
|
436
|
-
" the 10
|
|
558
|
+
" the 10 resource ints + the accepting player's color.\n"
|
|
437
559
|
" CLI: clawtan act CONFIRM_TRADE '[0,0,0,1,0,0,1,0,0,0,\"BLUE\"]'"
|
|
438
560
|
),
|
|
439
561
|
"CANCEL_TRADE": (
|
|
@@ -599,6 +721,37 @@ def _print_actions(actions: list, my_color: str | None = None, state: dict | Non
|
|
|
599
721
|
print(f"\n (other players still need to act: {', '.join(sorted(other_colors))})")
|
|
600
722
|
|
|
601
723
|
|
|
724
|
+
def _print_trade_context(state: dict, my_color: str):
|
|
725
|
+
"""Show the active trade offer and who has accepted so far."""
|
|
726
|
+
trade = state.get("current_trade")
|
|
727
|
+
if not trade or not isinstance(trade, list) or len(trade) < 10:
|
|
728
|
+
return
|
|
729
|
+
|
|
730
|
+
prompt = state.get("current_prompt", "")
|
|
731
|
+
colors = state.get("colors", [])
|
|
732
|
+
offerer_idx = trade[10] if len(trade) > 10 else None
|
|
733
|
+
offerer = colors[offerer_idx] if offerer_idx is not None and offerer_idx < len(colors) else "?"
|
|
734
|
+
|
|
735
|
+
_section("Active Trade")
|
|
736
|
+
print(f" Offered by: {offerer}")
|
|
737
|
+
print(f" Trade: {_format_trade_tuple(trade)}")
|
|
738
|
+
|
|
739
|
+
if prompt == "DECIDE_TRADE":
|
|
740
|
+
if offerer == my_color:
|
|
741
|
+
print(" Waiting for other players to accept or reject.")
|
|
742
|
+
else:
|
|
743
|
+
print(" You must ACCEPT_TRADE or REJECT_TRADE.")
|
|
744
|
+
elif prompt == "DECIDE_ACCEPTEES":
|
|
745
|
+
acceptees = state.get("acceptees", [])
|
|
746
|
+
accepted = [colors[i] for i, a in enumerate(acceptees) if a and i < len(colors)]
|
|
747
|
+
if accepted:
|
|
748
|
+
print(f" Accepted by: {', '.join(accepted)}")
|
|
749
|
+
else:
|
|
750
|
+
print(" No one accepted.")
|
|
751
|
+
if offerer == my_color:
|
|
752
|
+
print(" You may CONFIRM_TRADE with an acceptee or CANCEL_TRADE.")
|
|
753
|
+
|
|
754
|
+
|
|
602
755
|
def _unpack_record(r):
|
|
603
756
|
"""Unpack an action record into (color, action_type, value).
|
|
604
757
|
|
|
@@ -709,12 +862,12 @@ def _format_live_action(color, action, val, state=None, pre_resources=None):
|
|
|
709
862
|
return f" [{ts}] {color} offered a trade"
|
|
710
863
|
|
|
711
864
|
if action == "ACCEPT_TRADE":
|
|
712
|
-
if isinstance(val, list) and len(val)
|
|
865
|
+
if isinstance(val, list) and len(val) >= 10:
|
|
713
866
|
return f" [{ts}] {color} accepted trade: {_format_trade_tuple(val)}"
|
|
714
867
|
return f" [{ts}] {color} accepted a trade"
|
|
715
868
|
|
|
716
869
|
if action == "REJECT_TRADE":
|
|
717
|
-
if isinstance(val, list) and len(val)
|
|
870
|
+
if isinstance(val, list) and len(val) >= 10:
|
|
718
871
|
return f" [{ts}] {color} rejected trade: {_format_trade_tuple(val)}"
|
|
719
872
|
return f" [{ts}] {color} rejected a trade"
|
|
720
873
|
|
|
@@ -865,7 +1018,9 @@ def cmd_wait(args):
|
|
|
865
1018
|
if status.get("winning_color"):
|
|
866
1019
|
_header("GAME OVER")
|
|
867
1020
|
winner = status["winning_color"]
|
|
1021
|
+
result = "won" if winner == color else "lost"
|
|
868
1022
|
print(f" Winner: {winner}")
|
|
1023
|
+
print(f" You ({color}): {result}")
|
|
869
1024
|
try:
|
|
870
1025
|
state = _get(f"/game/{game_id}")
|
|
871
1026
|
colors = state.get("colors", [])
|
|
@@ -877,7 +1032,8 @@ def cmd_wait(args):
|
|
|
877
1032
|
print(f" {c}: {vp} VP{marker}")
|
|
878
1033
|
except (APIError, Exception):
|
|
879
1034
|
pass
|
|
880
|
-
|
|
1035
|
+
print("\n Game is finished. Do not call 'wait' or 'act' again for this game.")
|
|
1036
|
+
sys.exit(2) # distinct exit code so agents can detect game-over
|
|
881
1037
|
|
|
882
1038
|
# Progress messages (to stderr so they don't pollute the briefing)
|
|
883
1039
|
if not status.get("started"):
|
|
@@ -957,6 +1113,9 @@ def cmd_wait(args):
|
|
|
957
1113
|
except (APIError, Exception):
|
|
958
1114
|
pass
|
|
959
1115
|
|
|
1116
|
+
if state.get("is_resolving_trade"):
|
|
1117
|
+
_print_trade_context(state, color)
|
|
1118
|
+
|
|
960
1119
|
actions = state.get("current_playable_actions", [])
|
|
961
1120
|
if actions:
|
|
962
1121
|
_print_actions(actions, my_color=color, state=state)
|
|
@@ -965,6 +1124,8 @@ def cmd_wait(args):
|
|
|
965
1124
|
if robber:
|
|
966
1125
|
print(f"\n Kraken: {robber}")
|
|
967
1126
|
|
|
1127
|
+
print("\n >>> YOUR TURN: pick an action above and run 'clawtan act <ACTION> [VALUE]'.")
|
|
1128
|
+
|
|
968
1129
|
|
|
969
1130
|
def _print_roll_result(state: dict, pre_resources: dict | None):
|
|
970
1131
|
"""Show dice result and per-player resource gains after a roll."""
|
|
@@ -1043,6 +1204,19 @@ def cmd_act(args):
|
|
|
1043
1204
|
current = state.get("current_color", "?")
|
|
1044
1205
|
actions = state.get("current_playable_actions", [])
|
|
1045
1206
|
|
|
1207
|
+
# Primary diagnosis: is it even our turn?
|
|
1208
|
+
if current != color:
|
|
1209
|
+
print(
|
|
1210
|
+
f" It is NOT your turn. Current turn: {current} (you are {color}).",
|
|
1211
|
+
file=sys.stderr,
|
|
1212
|
+
)
|
|
1213
|
+
print(
|
|
1214
|
+
" >>> Run 'clawtan wait' to block until it is your turn.",
|
|
1215
|
+
file=sys.stderr,
|
|
1216
|
+
)
|
|
1217
|
+
sys.exit(1)
|
|
1218
|
+
|
|
1219
|
+
# It is our turn but wrong action/value
|
|
1046
1220
|
available_types = set()
|
|
1047
1221
|
for a in actions:
|
|
1048
1222
|
if isinstance(a, list) and len(a) > 1:
|
|
@@ -1056,17 +1230,22 @@ def cmd_act(args):
|
|
|
1056
1230
|
)
|
|
1057
1231
|
else:
|
|
1058
1232
|
print(
|
|
1059
|
-
f" '{args.action}' is not available right now.",
|
|
1233
|
+
f" '{args.action}' is not available right now (prompt: {prompt}).",
|
|
1060
1234
|
file=sys.stderr,
|
|
1061
1235
|
)
|
|
1062
1236
|
|
|
1063
1237
|
print(f" Current turn: {current} | Prompt: {prompt}", file=sys.stderr)
|
|
1064
1238
|
if actions:
|
|
1065
1239
|
_print_actions(actions, my_color=color, state=state)
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1240
|
+
print(
|
|
1241
|
+
"\n >>> Pick an action above and run 'clawtan act <ACTION> [VALUE]'.",
|
|
1242
|
+
file=sys.stderr,
|
|
1243
|
+
)
|
|
1244
|
+
else:
|
|
1245
|
+
print(
|
|
1246
|
+
"\n >>> No actions available. Run 'clawtan wait' for your next turn.",
|
|
1247
|
+
file=sys.stderr,
|
|
1248
|
+
)
|
|
1070
1249
|
except Exception:
|
|
1071
1250
|
print(
|
|
1072
1251
|
" Run 'clawtan wait' to see your available actions.",
|
|
@@ -1082,6 +1261,15 @@ def cmd_act(args):
|
|
|
1082
1261
|
|
|
1083
1262
|
# Re-fetch state so the agent knows what to do next
|
|
1084
1263
|
state = _get(f"/game/{game_id}")
|
|
1264
|
+
|
|
1265
|
+
# Check if this action ended the game (e.g. winning build)
|
|
1266
|
+
winner = state.get("winning_color")
|
|
1267
|
+
if winner:
|
|
1268
|
+
result = "won" if winner == color else "lost"
|
|
1269
|
+
print(f"\n GAME OVER — Winner: {winner} (you {result})")
|
|
1270
|
+
print(" Game is finished. Do not call 'wait' or 'act' again for this game.")
|
|
1271
|
+
sys.exit(2)
|
|
1272
|
+
|
|
1085
1273
|
current_color = state.get("current_color")
|
|
1086
1274
|
|
|
1087
1275
|
# After a roll, show the dice result and resource distribution
|
|
@@ -1106,17 +1294,23 @@ def cmd_act(args):
|
|
|
1106
1294
|
_section("Resources")
|
|
1107
1295
|
_print_resources(my["resources"])
|
|
1108
1296
|
|
|
1297
|
+
if state.get("is_resolving_trade"):
|
|
1298
|
+
_print_trade_context(state, color)
|
|
1299
|
+
|
|
1109
1300
|
if my_actions:
|
|
1110
1301
|
_print_actions(actions, my_color=color, state=state)
|
|
1302
|
+
print("\n >>> YOUR TURN: pick an action above and run 'clawtan act <ACTION> [VALUE]'.")
|
|
1111
1303
|
else:
|
|
1112
1304
|
print("\n No actions available.")
|
|
1305
|
+
print("\n >>> Run 'clawtan wait' to block until your next turn.")
|
|
1113
1306
|
elif my_actions:
|
|
1114
|
-
# We have actions even though current_color is someone else
|
|
1115
|
-
# (e.g. we also need to discard on a 7)
|
|
1116
1307
|
print(f" Prompt: {prompt}")
|
|
1308
|
+
if state.get("is_resolving_trade"):
|
|
1309
|
+
_print_trade_context(state, color)
|
|
1117
1310
|
_print_actions(actions, my_color=color, state=state)
|
|
1311
|
+
print("\n >>> ACTION REQUIRED: pick an action above and run 'clawtan act <ACTION> [VALUE]'.")
|
|
1118
1312
|
else:
|
|
1119
|
-
print(
|
|
1313
|
+
print("\n >>> Turn complete. Run 'clawtan wait' to block until your next turn.")
|
|
1120
1314
|
|
|
1121
1315
|
|
|
1122
1316
|
def cmd_status(args):
|
|
@@ -1412,8 +1606,8 @@ def main():
|
|
|
1412
1606
|
" PLAY_CURRENT_BUILDING Road Building\n"
|
|
1413
1607
|
" OFFER_TRADE <val> Player trade: 10-element list [give5, want5]\n"
|
|
1414
1608
|
" e.g. '[0,0,0,1,0,0,1,0,0,0]' = give 1 KELP, want 1 CORAL\n"
|
|
1415
|
-
" ACCEPT_TRADE <val> Accept
|
|
1416
|
-
" REJECT_TRADE <val> Reject
|
|
1609
|
+
" ACCEPT_TRADE <val> Accept trade (echo the 11-element value from actions)\n"
|
|
1610
|
+
" REJECT_TRADE <val> Reject trade (echo the 11-element value from actions)\n"
|
|
1417
1611
|
" CONFIRM_TRADE <val> Confirm with acceptee: 10 ints + color, e.g.\n"
|
|
1418
1612
|
" '[0,0,0,1,0,0,1,0,0,0,\"BLUE\"]'\n"
|
|
1419
1613
|
" CANCEL_TRADE Cancel your pending trade offer\n"
|
|
@@ -1484,6 +1678,38 @@ def main():
|
|
|
1484
1678
|
p.add_argument("--since", type=int, default=0, help="Only messages with index >= N (default: 0)")
|
|
1485
1679
|
p.set_defaults(func=cmd_chat_read)
|
|
1486
1680
|
|
|
1681
|
+
# -- whoami --------------------------------------------------------
|
|
1682
|
+
p = sub.add_parser(
|
|
1683
|
+
"whoami",
|
|
1684
|
+
help="Show the currently resolved session",
|
|
1685
|
+
description=(
|
|
1686
|
+
"Show which game, color, and token the CLI would use right now.\n"
|
|
1687
|
+
"Useful for verifying you're pointed at the correct game before acting."
|
|
1688
|
+
),
|
|
1689
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1690
|
+
)
|
|
1691
|
+
p.add_argument("--game", help="Game ID (or set CLAWTAN_GAME)")
|
|
1692
|
+
p.add_argument("--token", help="Auth token (or set CLAWTAN_TOKEN)")
|
|
1693
|
+
p.add_argument("--color", help="Your color (or set CLAWTAN_COLOR)")
|
|
1694
|
+
p.set_defaults(func=cmd_whoami)
|
|
1695
|
+
|
|
1696
|
+
# -- clear-session -------------------------------------------------
|
|
1697
|
+
p = sub.add_parser(
|
|
1698
|
+
"clear-session",
|
|
1699
|
+
help="Remove saved session files",
|
|
1700
|
+
description=(
|
|
1701
|
+
"Remove saved session files to avoid stale credentials.\n"
|
|
1702
|
+
"With no flags, removes the default ~/.clawtan_session file.\n"
|
|
1703
|
+
"Use --game/--color to remove specific session files.\n"
|
|
1704
|
+
"Use --all to remove every saved session."
|
|
1705
|
+
),
|
|
1706
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1707
|
+
)
|
|
1708
|
+
p.add_argument("--game", help="Only clear sessions for this game ID")
|
|
1709
|
+
p.add_argument("--color", help="Only clear sessions for this color")
|
|
1710
|
+
p.add_argument("--all", action="store_true", help="Remove ALL saved sessions")
|
|
1711
|
+
p.set_defaults(func=cmd_clear_session)
|
|
1712
|
+
|
|
1487
1713
|
# -- Parse and run -------------------------------------------------
|
|
1488
1714
|
args = parser.parse_args()
|
|
1489
1715
|
if args.player:
|