irregular6612 Claude Opus 4.8 (1M context) commited on
Commit
2d83c9f
·
1 Parent(s): 426093b

refactor(cli): split cli.py god-file into cli/ package (commands/ + parser)

Browse files
proteus/cli/__init__.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli — command dispatch. Behavior identical to the former cli.py.
2
+
3
+ Subcommands:
4
+ run Run one session and append its trace to a JSONL file.
5
+ list-scenarios List the registered scenario names.
6
+ replay Print a saved trace's turns, outcome, and metrics.
7
+ compare Aggregate traces by (model, difficulty) for baseline comparison.
8
+
9
+ All commands are network-free except ``run`` with a real provider spec; use
10
+ ``--model fake:<name>`` for an offline smoke.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import proteus.game.scenarios # noqa: F401 — side-effect: populate the scenario registry
15
+
16
+ from proteus.cli.parser import build_parser
17
+
18
+
19
+ def main(argv: list[str] | None = None) -> int:
20
+ """CLI entry point. Returns a process exit code."""
21
+ parser = build_parser()
22
+ args = parser.parse_args(argv)
23
+ return args.func(args)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ raise SystemExit(main())
proteus/cli/commands/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """proteus.cli.commands — one module per subcommand handler."""
proteus/cli/commands/compare.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.compare — the ``compare`` subcommand (aggregate traces)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from proteus.game.runtime import read_traces
9
+
10
+
11
+ def _cmd_compare(args: argparse.Namespace) -> int:
12
+ import json
13
+
14
+ from proteus.game.metrics.aggregate import aggregate_traces
15
+
16
+ all_traces = []
17
+ for path in args.trace_files:
18
+ try:
19
+ all_traces.extend(read_traces(path))
20
+ except FileNotFoundError:
21
+ print(f"trace file not found: {path}", file=sys.stderr)
22
+ return 2
23
+ if not all_traces:
24
+ print("no traces to compare", file=sys.stderr)
25
+ return 1
26
+
27
+ groups = aggregate_traces(all_traces)
28
+ for (model, difficulty), info in sorted(groups.items()):
29
+ print(f"=== model={model} difficulty={difficulty} n={info['n']} ===")
30
+ for key, mean in info["metrics"].items():
31
+ print(f" {key}: {mean:.2f}")
32
+
33
+ if args.out:
34
+ serializable = {
35
+ f"{model}|{difficulty}": info
36
+ for (model, difficulty), info in groups.items()
37
+ }
38
+ with open(args.out, "w") as fh:
39
+ json.dump(serializable, fh, indent=2)
40
+ print(f"summary written to {args.out}")
41
+ return 0
proteus/cli/commands/list_scenarios.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.list_scenarios — the ``list-scenarios`` subcommand."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from proteus.game.scenarios.base import list_scenarios
8
+
9
+
10
+ def _cmd_list_scenarios(_args: argparse.Namespace) -> int:
11
+ for name in list_scenarios():
12
+ print(name)
13
+ return 0
proteus/cli/commands/memory.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.memory — the ``memory`` subcommand (generate + save checkpoint)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from proteus.game.agents import VanillaAgent
9
+ from proteus.game.engine.difficulty import Difficulty
10
+ from proteus.game.scenarios.base import list_scenarios
11
+ from proteus.providers import make_provider
12
+ from proteus.game.runtime.memory import save_checkpoint
13
+ from proteus.game.runtime.memory_gen import generate_memory
14
+ from proteus.cli.commands.run import _resolve_persona
15
+
16
+
17
+ def _cmd_memory(args: argparse.Namespace) -> int:
18
+ try:
19
+ provider = make_provider(args.model)
20
+ except ValueError as exc:
21
+ print(str(exc), file=sys.stderr)
22
+ return 2
23
+ if args.scenario not in list_scenarios():
24
+ print(
25
+ f"Unknown scenario {args.scenario!r}. "
26
+ f"Available scenarios: {', '.join(list_scenarios())}.",
27
+ file=sys.stderr,
28
+ )
29
+ return 2
30
+ persona, persona_err = _resolve_persona(args.persona)
31
+ if persona_err is not None:
32
+ print(persona_err, file=sys.stderr)
33
+ return 2
34
+ agent = VanillaAgent(provider)
35
+ ckpt = generate_memory(
36
+ args.scenario, agent,
37
+ difficulty=Difficulty(args.difficulty), seed=args.seed,
38
+ memory_turns=args.memory_turns, model_name=provider.model_name,
39
+ persona=persona,
40
+ )
41
+ if args.out:
42
+ from pathlib import Path
43
+
44
+ path = Path(args.out)
45
+ path.parent.mkdir(parents=True, exist_ok=True)
46
+ path.write_text(ckpt.model_dump_json(), encoding="utf-8")
47
+ written = path
48
+ else:
49
+ written = save_checkpoint(ckpt, root=args.memory_root)
50
+ print(
51
+ f"memory {ckpt.scenario} seed={ckpt.seed} {ckpt.difficulty} "
52
+ f"model={ckpt.model} turns={len(ckpt.memory_turns)} -> {ckpt.outcome}"
53
+ )
54
+ print(f"checkpoint written to {written}")
55
+ return 0
proteus/cli/commands/play.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.play — the ``play`` subcommand (human via stdin)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from proteus.game.agents import HumanAgent
9
+ from proteus.game.engine.difficulty import Difficulty
10
+ from proteus.game.scenarios.base import list_scenarios
11
+ from proteus.game.runtime import SessionRunner, append_trace
12
+ from proteus.cli.commands.run import _auto_gif
13
+
14
+
15
+ def _cmd_play(args: argparse.Namespace) -> int:
16
+ if args.scenario not in list_scenarios():
17
+ print(
18
+ f"Unknown scenario {args.scenario!r}. "
19
+ f"Available scenarios: {', '.join(list_scenarios())}.",
20
+ file=sys.stderr,
21
+ )
22
+ return 2
23
+ agent = HumanAgent()
24
+ runner = SessionRunner(
25
+ args.scenario,
26
+ agent,
27
+ difficulty=Difficulty(args.difficulty),
28
+ seed=args.seed,
29
+ play_turns=args.play_turns,
30
+ use_probe=args.probe,
31
+ )
32
+ try:
33
+ trace = runner.run()
34
+ except EOFError:
35
+ print("stdin closed before the session finished.", file=sys.stderr)
36
+ return 2
37
+ accuracy = trace.metrics["motive_reading_accuracy"]
38
+ print(
39
+ f"{trace.scenario} seed={trace.seed} {trace.difficulty} "
40
+ f"model={trace.model} -> {trace.outcome} "
41
+ f"| motive_reading_accuracy={accuracy:.1f}% "
42
+ f"| reactivity_index={trace.metrics['reactivity_index']:.1f}%"
43
+ )
44
+ if args.out:
45
+ written = append_trace(trace, args.out)
46
+ print(f"trace appended to {written}")
47
+ _auto_gif(args.out, args.no_gif)
48
+ return 0
proteus/cli/commands/replay.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.replay — the ``replay`` subcommand (print a saved trace)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from proteus.game.runtime import read_traces
9
+
10
+
11
+ def _cmd_replay(args: argparse.Namespace) -> int:
12
+ try:
13
+ traces = read_traces(args.trace_file)
14
+ except FileNotFoundError:
15
+ print(f"trace file not found: {args.trace_file}", file=sys.stderr)
16
+ return 2
17
+ if not traces:
18
+ print(f"no traces in {args.trace_file}", file=sys.stderr)
19
+ return 1
20
+
21
+ if args.visual or args.png:
22
+ from proteus.game.viz import reconstruct, render_terminal, write_pngs
23
+
24
+ for trace in traces:
25
+ steps = reconstruct(trace)
26
+ if args.visual:
27
+ render_terminal(steps, fps=args.fps)
28
+ if args.png:
29
+ paths = write_pngs(steps, args.png)
30
+ print(f"wrote {len(paths)} PNG frames to {args.png}")
31
+ return 0
32
+
33
+ for trace in traces:
34
+ print(
35
+ f"=== {trace.scenario} seed={trace.seed} {trace.difficulty} "
36
+ f"model={trace.model} -> {trace.outcome} ==="
37
+ )
38
+ for turn in trace.turns:
39
+ tag = "congruent" if turn.was_congruent else "DIVERGED"
40
+ diag = " [diagnostic]" if turn.is_diagnostic else ""
41
+ print(
42
+ f" turn {turn.turn_idx}: action={turn.action} "
43
+ f"motive={turn.motive_action} habit={turn.habit_action} "
44
+ f"{tag}{diag} reward={turn.reward}"
45
+ )
46
+ print(f" metrics: {trace.metrics}")
47
+ return 0
proteus/cli/commands/run.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """proteus.cli.commands.run — the ``run`` subcommand and shared helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from proteus.game.agents import VanillaAgent
9
+ from proteus.game.engine.difficulty import Difficulty
10
+ from proteus.game.scenarios.base import list_scenarios
11
+ from proteus.providers import make_provider
12
+ from proteus.game.runtime import SessionRunner, append_trace, read_traces
13
+ from proteus.game.runtime.memory import (
14
+ latest_for_model,
15
+ load_checkpoint,
16
+ save_checkpoint,
17
+ )
18
+ from proteus.game.runtime.memory_gen import generate_memory
19
+ from proteus.game.metrics.persona import available_personas, get_persona
20
+
21
+
22
+ def _cmd_run(args: argparse.Namespace) -> int:
23
+ try:
24
+ provider = make_provider(args.model)
25
+ except ValueError as exc:
26
+ print(str(exc), file=sys.stderr)
27
+ return 2
28
+ if args.scenario not in list_scenarios():
29
+ print(
30
+ f"Unknown scenario {args.scenario!r}. "
31
+ f"Available scenarios: {', '.join(list_scenarios())}.",
32
+ file=sys.stderr,
33
+ )
34
+ return 2
35
+ agent = VanillaAgent(provider)
36
+ memory, memory_ref, mem_err = _resolve_memory(
37
+ args.memory, provider=provider, agent=agent, scenario=args.scenario,
38
+ difficulty=Difficulty(args.difficulty), seed=args.seed,
39
+ memory_turns=args.memory_turns, memory_root=args.memory_root,
40
+ )
41
+ if mem_err is not None:
42
+ print(mem_err, file=sys.stderr)
43
+ return 2
44
+ persona, persona_err = _resolve_persona(args.persona)
45
+ if persona_err is not None:
46
+ print(persona_err, file=sys.stderr)
47
+ return 2
48
+ runner = SessionRunner(
49
+ args.scenario,
50
+ agent,
51
+ difficulty=Difficulty(args.difficulty),
52
+ seed=args.seed,
53
+ play_turns=args.play_turns,
54
+ use_probe=not args.no_probe,
55
+ memory=memory,
56
+ memory_ref=memory_ref,
57
+ persona=persona,
58
+ )
59
+ trace = runner.run()
60
+ written = append_trace(trace, args.out)
61
+ accuracy = trace.metrics["motive_reading_accuracy"]
62
+ print(
63
+ f"{trace.scenario} seed={trace.seed} {trace.difficulty} "
64
+ f"model={trace.model} -> {trace.outcome} "
65
+ f"| motive_reading_accuracy={accuracy:.1f}% "
66
+ f"| reactivity_index={trace.metrics['reactivity_index']:.1f}%"
67
+ )
68
+ print(f"trace appended to {written}")
69
+ _auto_gif(args.out, args.no_gif)
70
+ return 0
71
+
72
+
73
+ def _resolve_persona(persona_id: str | None):
74
+ """Resolve --persona ID to (PersonaWeights | None, error | None)."""
75
+ if persona_id is None:
76
+ return None, None
77
+ try:
78
+ return get_persona(persona_id), None
79
+ except KeyError:
80
+ return None, (
81
+ f"Unknown persona {persona_id!r}. "
82
+ f"Available personas: {', '.join(available_personas())}."
83
+ )
84
+
85
+
86
+ def _auto_gif(out_path: str | None, no_gif: bool) -> None:
87
+ """Render <out_stem>.gif next to the trace file unless suppressed."""
88
+ if not out_path or no_gif:
89
+ return
90
+ from pathlib import Path
91
+
92
+ from proteus.game.viz import reconstruct, write_gif
93
+
94
+ gif_path = Path(out_path).with_suffix(".gif")
95
+ for trace in read_traces(out_path):
96
+ write_gif(reconstruct(trace), gif_path)
97
+ print(f"gif written to {gif_path}")
98
+ break # one game per run/play -> one GIF
99
+
100
+
101
+ def _resolve_memory(
102
+ mode: str, *, provider, agent, scenario: str, difficulty: Difficulty,
103
+ seed: int | None, memory_turns: int, memory_root: str,
104
+ ):
105
+ """Resolve --memory MODE to (checkpoint | None, memory_ref | None, error | None)."""
106
+ if mode == "none":
107
+ return None, None, None
108
+ if mode == "generate":
109
+ ckpt = generate_memory(
110
+ scenario, agent, difficulty=difficulty, seed=seed,
111
+ memory_turns=memory_turns, model_name=provider.model_name,
112
+ )
113
+ path = save_checkpoint(ckpt, root=memory_root)
114
+ return ckpt, str(path), None
115
+ if mode == "latest":
116
+ ckpt = latest_for_model(provider.model_name, root=memory_root)
117
+ if ckpt is None:
118
+ return None, None, (
119
+ f"no memory checkpoint for model {provider.model_name!r} "
120
+ f"under {memory_root!r}"
121
+ )
122
+ return ckpt, f"{ckpt.model}@{ckpt.created_at}", None
123
+ # otherwise treat MODE as a checkpoint path
124
+ try:
125
+ ckpt = load_checkpoint(mode)
126
+ except FileNotFoundError:
127
+ return None, None, f"memory checkpoint not found: {mode}"
128
+ return ckpt, mode, None
proteus/{cli.py → cli/parser.py} RENAMED
@@ -1,297 +1,19 @@
1
- """Command-line interface for the PROTEUS arena.
2
-
3
- Subcommands:
4
- run Run one session and append its trace to a JSONL file.
5
- list-scenarios List the registered scenario names.
6
- replay Print a saved trace's turns, outcome, and metrics.
7
- compare Aggregate traces by (model, difficulty) for baseline comparison.
8
-
9
- All commands are network-free except ``run`` with a real provider spec; use
10
- ``--model fake:<name>`` for an offline smoke.
11
- """
12
 
13
  from __future__ import annotations
14
 
15
  import argparse
16
- import sys
17
 
18
- import proteus.game.scenarios # noqa: F401 — side-effect: populate the scenario registry
19
- from proteus.game.agents import HumanAgent, VanillaAgent
20
  from proteus.game.engine.difficulty import Difficulty
21
- from proteus.game.scenarios.base import list_scenarios
22
- from proteus.providers import available_providers, make_provider
23
- from proteus.game.runtime import SessionRunner, append_trace, read_traces
24
- from proteus.game.runtime.memory import (
25
- latest_for_model,
26
- load_checkpoint,
27
- save_checkpoint,
28
- )
29
- from proteus.game.runtime.memory_gen import generate_memory
30
- from proteus.game.metrics.persona import available_personas, get_persona
31
-
32
-
33
- def _cmd_run(args: argparse.Namespace) -> int:
34
- try:
35
- provider = make_provider(args.model)
36
- except ValueError as exc:
37
- print(str(exc), file=sys.stderr)
38
- return 2
39
- if args.scenario not in list_scenarios():
40
- print(
41
- f"Unknown scenario {args.scenario!r}. "
42
- f"Available scenarios: {', '.join(list_scenarios())}.",
43
- file=sys.stderr,
44
- )
45
- return 2
46
- agent = VanillaAgent(provider)
47
- memory, memory_ref, mem_err = _resolve_memory(
48
- args.memory, provider=provider, agent=agent, scenario=args.scenario,
49
- difficulty=Difficulty(args.difficulty), seed=args.seed,
50
- memory_turns=args.memory_turns, memory_root=args.memory_root,
51
- )
52
- if mem_err is not None:
53
- print(mem_err, file=sys.stderr)
54
- return 2
55
- persona, persona_err = _resolve_persona(args.persona)
56
- if persona_err is not None:
57
- print(persona_err, file=sys.stderr)
58
- return 2
59
- runner = SessionRunner(
60
- args.scenario,
61
- agent,
62
- difficulty=Difficulty(args.difficulty),
63
- seed=args.seed,
64
- play_turns=args.play_turns,
65
- use_probe=not args.no_probe,
66
- memory=memory,
67
- memory_ref=memory_ref,
68
- persona=persona,
69
- )
70
- trace = runner.run()
71
- written = append_trace(trace, args.out)
72
- accuracy = trace.metrics["motive_reading_accuracy"]
73
- print(
74
- f"{trace.scenario} seed={trace.seed} {trace.difficulty} "
75
- f"model={trace.model} -> {trace.outcome} "
76
- f"| motive_reading_accuracy={accuracy:.1f}% "
77
- f"| reactivity_index={trace.metrics['reactivity_index']:.1f}%"
78
- )
79
- print(f"trace appended to {written}")
80
- _auto_gif(args.out, args.no_gif)
81
- return 0
82
-
83
-
84
- def _resolve_persona(persona_id: str | None):
85
- """Resolve --persona ID to (PersonaWeights | None, error | None)."""
86
- if persona_id is None:
87
- return None, None
88
- try:
89
- return get_persona(persona_id), None
90
- except KeyError:
91
- return None, (
92
- f"Unknown persona {persona_id!r}. "
93
- f"Available personas: {', '.join(available_personas())}."
94
- )
95
-
96
-
97
- def _auto_gif(out_path: str | None, no_gif: bool) -> None:
98
- """Render <out_stem>.gif next to the trace file unless suppressed."""
99
- if not out_path or no_gif:
100
- return
101
- from pathlib import Path
102
-
103
- from proteus.game.viz import reconstruct, write_gif
104
-
105
- gif_path = Path(out_path).with_suffix(".gif")
106
- for trace in read_traces(out_path):
107
- write_gif(reconstruct(trace), gif_path)
108
- print(f"gif written to {gif_path}")
109
- break # one game per run/play -> one GIF
110
-
111
-
112
- def _resolve_memory(
113
- mode: str, *, provider, agent, scenario: str, difficulty: Difficulty,
114
- seed: int | None, memory_turns: int, memory_root: str,
115
- ):
116
- """Resolve --memory MODE to (checkpoint | None, memory_ref | None, error | None)."""
117
- if mode == "none":
118
- return None, None, None
119
- if mode == "generate":
120
- ckpt = generate_memory(
121
- scenario, agent, difficulty=difficulty, seed=seed,
122
- memory_turns=memory_turns, model_name=provider.model_name,
123
- )
124
- path = save_checkpoint(ckpt, root=memory_root)
125
- return ckpt, str(path), None
126
- if mode == "latest":
127
- ckpt = latest_for_model(provider.model_name, root=memory_root)
128
- if ckpt is None:
129
- return None, None, (
130
- f"no memory checkpoint for model {provider.model_name!r} "
131
- f"under {memory_root!r}"
132
- )
133
- return ckpt, f"{ckpt.model}@{ckpt.created_at}", None
134
- # otherwise treat MODE as a checkpoint path
135
- try:
136
- ckpt = load_checkpoint(mode)
137
- except FileNotFoundError:
138
- return None, None, f"memory checkpoint not found: {mode}"
139
- return ckpt, mode, None
140
-
141
-
142
- def _cmd_memory(args: argparse.Namespace) -> int:
143
- try:
144
- provider = make_provider(args.model)
145
- except ValueError as exc:
146
- print(str(exc), file=sys.stderr)
147
- return 2
148
- if args.scenario not in list_scenarios():
149
- print(
150
- f"Unknown scenario {args.scenario!r}. "
151
- f"Available scenarios: {', '.join(list_scenarios())}.",
152
- file=sys.stderr,
153
- )
154
- return 2
155
- persona, persona_err = _resolve_persona(args.persona)
156
- if persona_err is not None:
157
- print(persona_err, file=sys.stderr)
158
- return 2
159
- agent = VanillaAgent(provider)
160
- ckpt = generate_memory(
161
- args.scenario, agent,
162
- difficulty=Difficulty(args.difficulty), seed=args.seed,
163
- memory_turns=args.memory_turns, model_name=provider.model_name,
164
- persona=persona,
165
- )
166
- if args.out:
167
- from pathlib import Path
168
-
169
- path = Path(args.out)
170
- path.parent.mkdir(parents=True, exist_ok=True)
171
- path.write_text(ckpt.model_dump_json(), encoding="utf-8")
172
- written = path
173
- else:
174
- written = save_checkpoint(ckpt, root=args.memory_root)
175
- print(
176
- f"memory {ckpt.scenario} seed={ckpt.seed} {ckpt.difficulty} "
177
- f"model={ckpt.model} turns={len(ckpt.memory_turns)} -> {ckpt.outcome}"
178
- )
179
- print(f"checkpoint written to {written}")
180
- return 0
181
-
182
-
183
- def _cmd_play(args: argparse.Namespace) -> int:
184
- if args.scenario not in list_scenarios():
185
- print(
186
- f"Unknown scenario {args.scenario!r}. "
187
- f"Available scenarios: {', '.join(list_scenarios())}.",
188
- file=sys.stderr,
189
- )
190
- return 2
191
- agent = HumanAgent()
192
- runner = SessionRunner(
193
- args.scenario,
194
- agent,
195
- difficulty=Difficulty(args.difficulty),
196
- seed=args.seed,
197
- play_turns=args.play_turns,
198
- use_probe=args.probe,
199
- )
200
- try:
201
- trace = runner.run()
202
- except EOFError:
203
- print("stdin closed before the session finished.", file=sys.stderr)
204
- return 2
205
- accuracy = trace.metrics["motive_reading_accuracy"]
206
- print(
207
- f"{trace.scenario} seed={trace.seed} {trace.difficulty} "
208
- f"model={trace.model} -> {trace.outcome} "
209
- f"| motive_reading_accuracy={accuracy:.1f}% "
210
- f"| reactivity_index={trace.metrics['reactivity_index']:.1f}%"
211
- )
212
- if args.out:
213
- written = append_trace(trace, args.out)
214
- print(f"trace appended to {written}")
215
- _auto_gif(args.out, args.no_gif)
216
- return 0
217
-
218
-
219
- def _cmd_list_scenarios(_args: argparse.Namespace) -> int:
220
- for name in list_scenarios():
221
- print(name)
222
- return 0
223
-
224
-
225
- def _cmd_replay(args: argparse.Namespace) -> int:
226
- try:
227
- traces = read_traces(args.trace_file)
228
- except FileNotFoundError:
229
- print(f"trace file not found: {args.trace_file}", file=sys.stderr)
230
- return 2
231
- if not traces:
232
- print(f"no traces in {args.trace_file}", file=sys.stderr)
233
- return 1
234
-
235
- if args.visual or args.png:
236
- from proteus.game.viz import reconstruct, render_terminal, write_pngs
237
 
238
- for trace in traces:
239
- steps = reconstruct(trace)
240
- if args.visual:
241
- render_terminal(steps, fps=args.fps)
242
- if args.png:
243
- paths = write_pngs(steps, args.png)
244
- print(f"wrote {len(paths)} PNG frames to {args.png}")
245
- return 0
246
-
247
- for trace in traces:
248
- print(
249
- f"=== {trace.scenario} seed={trace.seed} {trace.difficulty} "
250
- f"model={trace.model} -> {trace.outcome} ==="
251
- )
252
- for turn in trace.turns:
253
- tag = "congruent" if turn.was_congruent else "DIVERGED"
254
- diag = " [diagnostic]" if turn.is_diagnostic else ""
255
- print(
256
- f" turn {turn.turn_idx}: action={turn.action} "
257
- f"motive={turn.motive_action} habit={turn.habit_action} "
258
- f"{tag}{diag} reward={turn.reward}"
259
- )
260
- print(f" metrics: {trace.metrics}")
261
- return 0
262
-
263
-
264
- def _cmd_compare(args: argparse.Namespace) -> int:
265
- import json
266
-
267
- from proteus.game.metrics.aggregate import aggregate_traces
268
-
269
- all_traces = []
270
- for path in args.trace_files:
271
- try:
272
- all_traces.extend(read_traces(path))
273
- except FileNotFoundError:
274
- print(f"trace file not found: {path}", file=sys.stderr)
275
- return 2
276
- if not all_traces:
277
- print("no traces to compare", file=sys.stderr)
278
- return 1
279
-
280
- groups = aggregate_traces(all_traces)
281
- for (model, difficulty), info in sorted(groups.items()):
282
- print(f"=== model={model} difficulty={difficulty} n={info['n']} ===")
283
- for key, mean in info["metrics"].items():
284
- print(f" {key}: {mean:.2f}")
285
-
286
- if args.out:
287
- serializable = {
288
- f"{model}|{difficulty}": info
289
- for (model, difficulty), info in groups.items()
290
- }
291
- with open(args.out, "w") as fh:
292
- json.dump(serializable, fh, indent=2)
293
- print(f"summary written to {args.out}")
294
- return 0
295
 
296
 
297
  def build_parser() -> argparse.ArgumentParser:
@@ -415,10 +137,3 @@ def build_parser() -> argparse.ArgumentParser:
415
  compare.set_defaults(func=_cmd_compare)
416
 
417
  return parser
418
-
419
-
420
- def main(argv: list[str] | None = None) -> int:
421
- """CLI entry point. Returns a process exit code."""
422
- parser = build_parser()
423
- args = parser.parse_args(argv)
424
- return args.func(args)
 
1
+ """proteus.cli.parser top-level argument parser construction."""
 
 
 
 
 
 
 
 
 
 
2
 
3
  from __future__ import annotations
4
 
5
  import argparse
 
6
 
 
 
7
  from proteus.game.engine.difficulty import Difficulty
8
+ from proteus.providers import available_providers
9
+ from proteus.game.metrics.persona import available_personas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ from proteus.cli.commands.run import _cmd_run
12
+ from proteus.cli.commands.play import _cmd_play
13
+ from proteus.cli.commands.memory import _cmd_memory
14
+ from proteus.cli.commands.list_scenarios import _cmd_list_scenarios
15
+ from proteus.cli.commands.replay import _cmd_replay
16
+ from proteus.cli.commands.compare import _cmd_compare
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
 
19
  def build_parser() -> argparse.ArgumentParser:
 
137
  compare.set_defaults(func=_cmd_compare)
138
 
139
  return parser