68 """Read *event_dir* and build the replay tree; raise :class:`_UserError` if missing."""
69 base = Path(event_dir)
71 raise _UserError(f
"event directory not found: {event_dir}")
72 events, skipped = read_events_from_dir(base)
73 tree = build_replay_tree(events, skipped=skipped)
74 tree.source_dir = str(base)
91 """Build the ``replay`` argument parser with its three subcommands."""
92 parser = argparse.ArgumentParser(
93 prog=
"python -m oc_chat.scripts.replay",
94 description=
"Offline replay of an OC Chat session from its event NDJSON (no provider calls).",
96 sub = parser.add_subparsers(dest=
"cmd", required=
True)
98 p_session = sub.add_parser(
"session", help=
"render the full session tree")
99 p_session.add_argument(
"event_dir")
100 p_session.add_argument(
"--json", action=
"store_true", help=
"emit canonical replay/v0 JSON")
101 p_session.add_argument(
"--limit", type=int, default=
None, help=
"render only the last N turns")
103 p_turn = sub.add_parser(
"turn", help=
"render one turn by id or >=4-char prefix")
104 p_turn.add_argument(
"event_dir")
105 p_turn.add_argument(
"turn_id")
106 p_turn.add_argument(
"--json", action=
"store_true", help=
"emit canonical replay/v0 JSON")
108 p_summary = sub.add_parser(
"summary", help=
"print per-kind counts + turn count only")
109 p_summary.add_argument(
"event_dir")
110 p_summary.add_argument(
"--json", action=
"store_true", help=
"emit canonical replay/v0 JSON")
115 """Print the cheap per-kind count view for *tree*."""
118 session_id=tree.session_id, turn_count=tree.turn_count,
119 event_count=tree.event_count, skipped_count=tree.skipped_count,
120 source_dir=tree.source_dir, event_counts=tree.event_counts, turns=[],
123 print(f
"── Replay summary: session {(tree.session_id or '-')[:8]} ──")
124 print(f
" events: {tree.event_count} turns: {tree.turn_count} skipped: {tree.skipped_count}")
125 for kind
in sorted(tree.event_counts):
126 print(f
" {kind:<24} {tree.event_counts[kind]}")
129def main(argv: Optional[Sequence[str]] =
None) -> int:
130 """Entry point. Returns the process exit code (0 ok, 1 error, 2 user error)."""
132 for _stream
in (sys.stdout, sys.stderr):
134 _stream.reconfigure(errors=
"replace")
135 except (AttributeError, ValueError):
138 args = parser.parse_args(argv)
140 _dbg(
"OC_CHAT.REPLAY", func, f
"cmd={args.cmd} dir={args.event_dir}", 2)
142 if args.cmd ==
"summary":
144 elif args.cmd ==
"turn":
146 print(render_tree_json(tree, single_turn_id=turn_id)
if args.json
147 else render_tree_text(tree, single_turn_id=turn_id))
149 if args.limit
is not None and args.limit >= 0:
150 tree.turns = tree.turns[-args.limit:]
151 print(render_tree_json(tree)
if args.json
else render_tree_text(tree))
153 except _UserError
as exc:
154 print(f
"error: {exc}", file=sys.stderr)
156 except Exception
as exc:
157 _dbg(
"OC_CHAT.REPLAY", func, f
"SYSTEM_ERROR: {type(exc).__name__}: {exc}", 1, override=
True)
158 print(f
"error: {type(exc).__name__}: {exc}", file=sys.stderr)