codex-transcript-viewer 0.4.0__py3-none-any.whl
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.
- codex_transcript_viewer/__init__.py +0 -0
- codex_transcript_viewer/cli.py +81 -0
- codex_transcript_viewer/formatting.py +23 -0
- codex_transcript_viewer/html_builder.py +769 -0
- codex_transcript_viewer/markdown.py +156 -0
- codex_transcript_viewer/parser.py +1204 -0
- codex_transcript_viewer/style.css +589 -0
- codex_transcript_viewer/viewer.js +63 -0
- codex_transcript_viewer-0.4.0.dist-info/METADATA +122 -0
- codex_transcript_viewer-0.4.0.dist-info/RECORD +13 -0
- codex_transcript_viewer-0.4.0.dist-info/WHEEL +4 -0
- codex_transcript_viewer-0.4.0.dist-info/entry_points.txt +2 -0
- codex_transcript_viewer-0.4.0.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Command-line interface for converting Codex CLI JSONL sessions to HTML."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .html_builder import DEFAULT_IMAGE_BUDGET_MB, DEFAULT_MAX_OUTPUT_CHARS, build_html
|
|
10
|
+
from .parser import extract_conversation, parse_jsonl, unrecognized_record_kinds
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="codex-transcript-viewer",
|
|
16
|
+
description="Convert a Codex CLI JSONL session into a self-contained HTML viewer.",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("session", type=Path, help="path to a rollout-*.jsonl session file")
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"output",
|
|
21
|
+
type=Path,
|
|
22
|
+
nargs="?",
|
|
23
|
+
help="output HTML path (default: <session-stem>.html in the current directory)",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--no-images",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="show images as labelled placeholders instead of embedding them",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--max-image-mb",
|
|
32
|
+
type=float,
|
|
33
|
+
default=DEFAULT_IMAGE_BUDGET_MB,
|
|
34
|
+
metavar="N",
|
|
35
|
+
help=(
|
|
36
|
+
"embed tool-output images until they total N MB, then show placeholders "
|
|
37
|
+
f"(default {DEFAULT_IMAGE_BUDGET_MB}; 0 means no limit). Prompt images are always embedded."
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--max-output-chars",
|
|
42
|
+
type=int,
|
|
43
|
+
default=DEFAULT_MAX_OUTPUT_CHARS,
|
|
44
|
+
metavar="N",
|
|
45
|
+
help=f"cap each tool output at N characters (default {DEFAULT_MAX_OUTPUT_CHARS:,}; 0 means no cap)",
|
|
46
|
+
)
|
|
47
|
+
return parser.parse_args(argv)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: list[str] | None = None) -> None:
|
|
51
|
+
args = _parse_args(argv)
|
|
52
|
+
|
|
53
|
+
inpath = args.session
|
|
54
|
+
if not inpath.exists():
|
|
55
|
+
print(f"error: {inpath} not found", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
outpath = args.output or Path(inpath.stem + ".html")
|
|
59
|
+
|
|
60
|
+
entries = parse_jsonl(inpath)
|
|
61
|
+
meta, events = extract_conversation(entries)
|
|
62
|
+
html_content = build_html(
|
|
63
|
+
meta,
|
|
64
|
+
events,
|
|
65
|
+
embed_images=not args.no_images,
|
|
66
|
+
max_image_mb=args.max_image_mb,
|
|
67
|
+
max_output_chars=args.max_output_chars,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
outpath.write_text(html_content, encoding="utf-8")
|
|
71
|
+
size = outpath.stat().st_size
|
|
72
|
+
print(f"written to {outpath} ({size:,} bytes, {len(events)} events)")
|
|
73
|
+
|
|
74
|
+
unknown = unrecognized_record_kinds(entries)
|
|
75
|
+
if unknown:
|
|
76
|
+
summary = ", ".join(f"{kind} x{count}" for kind, count in unknown.most_common())
|
|
77
|
+
print(f"skipped unrecognized records: {summary}", file=sys.stderr)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Timestamp and text formatting utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_ts(ts_str: str) -> str:
|
|
9
|
+
"""Format an ISO timestamp to HH:MM:SS for inline display."""
|
|
10
|
+
try:
|
|
11
|
+
dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
|
|
12
|
+
return dt.strftime("%H:%M:%S")
|
|
13
|
+
except (ValueError, TypeError):
|
|
14
|
+
return ts_str[:19] if ts_str else ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_ts_full(ts_str: str) -> str:
|
|
18
|
+
"""Format an ISO timestamp to a full human-readable UTC string."""
|
|
19
|
+
try:
|
|
20
|
+
dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
|
|
21
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
22
|
+
except (ValueError, TypeError):
|
|
23
|
+
return ts_str or ""
|