clawtan 0.2.0 → 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/clawtan/cli.py +118 -32
- package/package.json +1 -1
package/clawtan/cli.py
CHANGED
|
@@ -14,10 +14,24 @@ Typical agent flow:
|
|
|
14
14
|
clawtan act END_TIDE
|
|
15
15
|
clawtan wait # next turn...
|
|
16
16
|
|
|
17
|
+
Multi-player on one machine (each terminal):
|
|
18
|
+
clawtan join <GAME_ID> # join, note your assigned color
|
|
19
|
+
export CLAWTAN_COLOR=<COLOR> # lock this terminal to your player
|
|
20
|
+
clawtan wait # now uses the correct session
|
|
21
|
+
|
|
22
|
+
Or use --player on every call (works with separate exec calls):
|
|
23
|
+
clawtan --player BLUE wait
|
|
24
|
+
clawtan --player BLUE act ROLL_THE_SHELLS
|
|
25
|
+
|
|
26
|
+
Same color in multiple games — add --game:
|
|
27
|
+
clawtan --player RED wait --game <GAME_ID>
|
|
28
|
+
|
|
17
29
|
Session lookup order (per field):
|
|
18
|
-
1. CLI flags (--game, --token, --color)
|
|
30
|
+
1. CLI flags (--game, --token, --color, --player)
|
|
19
31
|
2. Environment variables (CLAWTAN_GAME, CLAWTAN_TOKEN, CLAWTAN_COLOR)
|
|
20
|
-
3. Session file
|
|
32
|
+
3. Session file: ~/.clawtan_sessions/<game>_<color>.json matched by
|
|
33
|
+
available hints, falling back to ~/.clawtan_session.
|
|
34
|
+
Override with CLAWTAN_SESSION_FILE.
|
|
21
35
|
"""
|
|
22
36
|
|
|
23
37
|
import argparse
|
|
@@ -126,42 +140,97 @@ def _get(path, token=None):
|
|
|
126
140
|
# ---------------------------------------------------------------------------
|
|
127
141
|
# Session file helpers
|
|
128
142
|
# ---------------------------------------------------------------------------
|
|
129
|
-
|
|
130
|
-
return os.environ.get("CLAWTAN_SESSION_FILE") or os.path.expanduser("~/.clawtan_session")
|
|
143
|
+
_SESSIONS_DIR = os.path.expanduser("~/.clawtan_sessions")
|
|
131
144
|
|
|
132
145
|
|
|
133
146
|
def _save_session(game_id: str, token: str, color: str):
|
|
134
|
-
path = _session_path()
|
|
135
147
|
data = {"GAME": game_id, "TOKEN": token, "COLOR": color}
|
|
148
|
+
os.makedirs(_SESSIONS_DIR, exist_ok=True)
|
|
149
|
+
path = os.path.join(_SESSIONS_DIR, f"{game_id}_{color}.json")
|
|
136
150
|
try:
|
|
137
151
|
with open(path, "w") as f:
|
|
138
152
|
json.dump(data, f)
|
|
139
153
|
except OSError as e:
|
|
140
154
|
print(f"Warning: could not save session to {path}: {e}", file=sys.stderr)
|
|
155
|
+
default = os.environ.get("CLAWTAN_SESSION_FILE") or os.path.expanduser("~/.clawtan_session")
|
|
156
|
+
try:
|
|
157
|
+
with open(default, "w") as f:
|
|
158
|
+
json.dump(data, f)
|
|
159
|
+
except OSError:
|
|
160
|
+
pass
|
|
141
161
|
|
|
142
162
|
|
|
143
|
-
def
|
|
144
|
-
|
|
163
|
+
def _find_session(game_hint: str | None = None, color_hint: str | None = None) -> dict:
|
|
164
|
+
"""Find a session file using available game/color hints."""
|
|
165
|
+
custom = os.environ.get("CLAWTAN_SESSION_FILE")
|
|
166
|
+
if custom:
|
|
167
|
+
try:
|
|
168
|
+
with open(custom) as f:
|
|
169
|
+
return json.load(f)
|
|
170
|
+
except (OSError, json.JSONDecodeError):
|
|
171
|
+
return {}
|
|
172
|
+
|
|
173
|
+
if game_hint and color_hint:
|
|
174
|
+
path = os.path.join(_SESSIONS_DIR, f"{game_hint}_{color_hint}.json")
|
|
175
|
+
try:
|
|
176
|
+
with open(path) as f:
|
|
177
|
+
return json.load(f)
|
|
178
|
+
except (OSError, json.JSONDecodeError):
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
if (game_hint or color_hint) and os.path.isdir(_SESSIONS_DIR):
|
|
182
|
+
matches = []
|
|
183
|
+
for fname in os.listdir(_SESSIONS_DIR):
|
|
184
|
+
if not fname.endswith(".json"):
|
|
185
|
+
continue
|
|
186
|
+
try:
|
|
187
|
+
with open(os.path.join(_SESSIONS_DIR, fname)) as f:
|
|
188
|
+
data = json.load(f)
|
|
189
|
+
if game_hint and data.get("GAME") != game_hint:
|
|
190
|
+
continue
|
|
191
|
+
if color_hint and data.get("COLOR") != color_hint:
|
|
192
|
+
continue
|
|
193
|
+
matches.append(data)
|
|
194
|
+
except (OSError, json.JSONDecodeError):
|
|
195
|
+
continue
|
|
196
|
+
if len(matches) == 1:
|
|
197
|
+
return matches[0]
|
|
198
|
+
if matches:
|
|
199
|
+
return matches[0]
|
|
200
|
+
|
|
201
|
+
default = os.path.expanduser("~/.clawtan_session")
|
|
145
202
|
try:
|
|
146
|
-
with open(
|
|
203
|
+
with open(default) as f:
|
|
147
204
|
return json.load(f)
|
|
148
205
|
except (OSError, json.JSONDecodeError):
|
|
149
206
|
return {}
|
|
150
207
|
|
|
151
208
|
|
|
152
209
|
# ---------------------------------------------------------------------------
|
|
153
|
-
#
|
|
210
|
+
# Session resolution
|
|
154
211
|
# ---------------------------------------------------------------------------
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
212
|
+
def _resolve_session(arg_game=None, arg_token=None, arg_color=None):
|
|
213
|
+
"""Resolve game, token, color from flags -> env vars -> session file.
|
|
214
|
+
|
|
215
|
+
Uses all available hints together to find the correct session file,
|
|
216
|
+
which avoids ambiguity when multiple games or players share a machine.
|
|
217
|
+
"""
|
|
218
|
+
game = arg_game or os.environ.get("CLAWTAN_GAME") or None
|
|
219
|
+
token = arg_token or os.environ.get("CLAWTAN_TOKEN") or None
|
|
220
|
+
color = arg_color or os.environ.get("CLAWTAN_COLOR") or None
|
|
221
|
+
|
|
222
|
+
if not (game and token and color):
|
|
223
|
+
session = _find_session(game_hint=game, color_hint=color)
|
|
224
|
+
game = game or session.get("GAME")
|
|
225
|
+
token = token or session.get("TOKEN")
|
|
226
|
+
color = color or session.get("COLOR")
|
|
227
|
+
|
|
228
|
+
return game, token, color
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _require(name: str, val):
|
|
232
|
+
"""Exit with error if a required session value is missing."""
|
|
162
233
|
if not val:
|
|
163
|
-
val = _load_session().get(name)
|
|
164
|
-
if required and not val:
|
|
165
234
|
print(
|
|
166
235
|
f"ERROR: Missing {name}. Pass --{name.lower()}, set CLAWTAN_{name},"
|
|
167
236
|
f" or run 'clawtan quick-join' to create a session.",
|
|
@@ -493,14 +562,20 @@ def _print_join(resp: dict):
|
|
|
493
562
|
print(f" Started: {'yes' if resp.get('game_started') else 'no'}")
|
|
494
563
|
|
|
495
564
|
_save_session(resp["game_id"], resp["token"], resp["player_color"])
|
|
496
|
-
|
|
497
|
-
|
|
565
|
+
gid = resp["game_id"]
|
|
566
|
+
color = resp["player_color"]
|
|
567
|
+
print(f"\n Session saved to ~/.clawtan_sessions/{gid}_{color}.json")
|
|
568
|
+
print(f"\n Multi-player setup — pick ONE option:")
|
|
569
|
+
print(f" export CLAWTAN_COLOR={color} # env var per terminal")
|
|
570
|
+
print(f" clawtan --player {color} <command> # flag per command")
|
|
571
|
+
print(f" clawtan --player {color} wait --game {gid} # if same color in multiple games")
|
|
498
572
|
|
|
499
573
|
|
|
500
574
|
def cmd_wait(args):
|
|
501
|
-
game_id =
|
|
502
|
-
|
|
503
|
-
|
|
575
|
+
game_id, token, color = _resolve_session(args.game, args.token, args.color)
|
|
576
|
+
_require("GAME", game_id)
|
|
577
|
+
_require("TOKEN", token)
|
|
578
|
+
_require("COLOR", color)
|
|
504
579
|
poll = args.poll
|
|
505
580
|
deadline = time.monotonic() + args.timeout
|
|
506
581
|
|
|
@@ -652,9 +727,10 @@ def _print_roll_result(state: dict, pre_resources: dict | None):
|
|
|
652
727
|
|
|
653
728
|
|
|
654
729
|
def cmd_act(args):
|
|
655
|
-
game_id =
|
|
656
|
-
|
|
657
|
-
|
|
730
|
+
game_id, token, color = _resolve_session(args.game, args.token, args.color)
|
|
731
|
+
_require("GAME", game_id)
|
|
732
|
+
_require("TOKEN", token)
|
|
733
|
+
_require("COLOR", color)
|
|
658
734
|
|
|
659
735
|
# Snapshot resources before rolling so we can diff afterwards
|
|
660
736
|
pre_resources = None
|
|
@@ -752,8 +828,8 @@ def cmd_act(args):
|
|
|
752
828
|
|
|
753
829
|
|
|
754
830
|
def cmd_status(args):
|
|
755
|
-
game_id =
|
|
756
|
-
|
|
831
|
+
game_id, token, _ = _resolve_session(args.game, args.token)
|
|
832
|
+
_require("GAME", game_id)
|
|
757
833
|
|
|
758
834
|
status = _get(f"/game/{game_id}/status", token=token)
|
|
759
835
|
|
|
@@ -777,7 +853,8 @@ def cmd_status(args):
|
|
|
777
853
|
|
|
778
854
|
|
|
779
855
|
def cmd_board(args):
|
|
780
|
-
game_id =
|
|
856
|
+
game_id, _, _ = _resolve_session(args.game)
|
|
857
|
+
_require("GAME", game_id)
|
|
781
858
|
state = _get(f"/game/{game_id}")
|
|
782
859
|
|
|
783
860
|
if not state.get("started") or not state.get("tiles"):
|
|
@@ -907,14 +984,16 @@ def cmd_board(args):
|
|
|
907
984
|
|
|
908
985
|
|
|
909
986
|
def cmd_chat(args):
|
|
910
|
-
game_id =
|
|
911
|
-
|
|
987
|
+
game_id, token, _ = _resolve_session(args.game, args.token)
|
|
988
|
+
_require("GAME", game_id)
|
|
989
|
+
_require("TOKEN", token)
|
|
912
990
|
_post(f"/game/{game_id}/chat", {"message": args.message}, token=token)
|
|
913
991
|
print("Chat sent.")
|
|
914
992
|
|
|
915
993
|
|
|
916
994
|
def cmd_chat_read(args):
|
|
917
|
-
game_id =
|
|
995
|
+
game_id, _, _ = _resolve_session(args.game)
|
|
996
|
+
_require("GAME", game_id)
|
|
918
997
|
resp = _get(f"/game/{game_id}/chat?since={args.since}")
|
|
919
998
|
msgs = resp.get("messages", [])
|
|
920
999
|
if msgs:
|
|
@@ -939,6 +1018,11 @@ def main():
|
|
|
939
1018
|
),
|
|
940
1019
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
941
1020
|
)
|
|
1021
|
+
parser.add_argument(
|
|
1022
|
+
"--player",
|
|
1023
|
+
metavar="COLOR",
|
|
1024
|
+
help="Player color — selects the per-player session file (for multi-player on one machine)",
|
|
1025
|
+
)
|
|
942
1026
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
943
1027
|
|
|
944
1028
|
# -- create --------------------------------------------------------
|
|
@@ -1088,6 +1172,8 @@ def main():
|
|
|
1088
1172
|
|
|
1089
1173
|
# -- Parse and run -------------------------------------------------
|
|
1090
1174
|
args = parser.parse_args()
|
|
1175
|
+
if args.player:
|
|
1176
|
+
os.environ["CLAWTAN_COLOR"] = args.player
|
|
1091
1177
|
try:
|
|
1092
1178
|
args.func(args)
|
|
1093
1179
|
except APIError as e:
|