scripts-extract-codex-session
#!/usr/bin/env python3 """Extract per-session JSONL from Codex history.jsonl."""
from future import annotations
import argparse import json from pathlib import Path from typing import Dict, Iterable, List, Tuple
def iter_jsonl(path: Path) -> Iterable[Dict]: with path.open(encoding='utf-8') as f: for line in f: if not line.strip(): continue yield json.loads(line)
def list_sessions(path: Path) -> List[str]: session_ids = set() for obj in iter_jsonl(path): sid = obj.get('session_id') if sid: session_ids.add(sid) return sorted(session_ids)
def export_session(path: Path, session_id: str, out_path: Path, dry_run: bool) -> int: count = 0 if not dry_run: out_path.parent.mkdir(parents=True, exist_ok=True) with path.open(encoding='utf-8') as f: lines = f.readlines() if dry_run: return sum(1 for line in lines if line.strip() and json.loads(line).get('session_id') == session_id) with out_path.open('w', encoding='utf-8') as out: for line in lines: if not line.strip(): continue obj = json.loads(line) if obj.get('session_id') == session_id: out.write(line) count += 1 return count
def export_all(path: Path, out_dir: Path, dry_run: bool) -> List[Tuple[str, int]]: results: List[Tuple[str, int]] = [] for sid in list_sessions(path): out_path = out_dir / f'session-{sid}.jsonl' count = export_session(path, sid, out_path, dry_run) results.append((sid, count)) return results
def main() -> None: parser = argparse.ArgumentParser(description='Extract per-session JSONL from Codex history.jsonl') parser.add_argument('--history', default=str(Path.home() / '.codex' / 'history.jsonl')) parser.add_argument('--list', action='store_true', help='List available session IDs') parser.add_argument('--session-id', help='Session ID to export') parser.add_argument('--output', help='Output JSONL path for single session') parser.add_argument('--all', action='store_true', help='Export all sessions') parser.add_argument('--out-dir', help='Output directory for --all') parser.add_argument('--dry-run', action='store_true', help='Show counts without writing files') args = parser.parse_args()
history = Path(args.history)
if not history.exists():
raise SystemExit(f'History file not found: {history}')
if args.list:
for sid in list_sessions(history):
print(sid)
return
if args.session_id:
if not args.output:
raise SystemExit('--output is required with --session-id')
out_path = Path(args.output)
count = export_session(history, args.session_id, out_path, args.dry_run)
if args.dry_run:
print(f'[dry-run] {args.session_id}: {count} lines')
else:
print(f'exported {count} lines -> {out_path}')
return
if args.all:
if not args.out_dir:
raise SystemExit('--out-dir is required with --all')
out_dir = Path(args.out_dir)
results = export_all(history, out_dir, args.dry_run)
for sid, count in results:
if args.dry_run:
print(f'[dry-run] {sid}: {count} lines')
else:
print(f'exported {sid}: {count} lines -> {out_dir / f"session-{sid}.jsonl"}')
return
raise SystemExit('Specify --list, --session-id, or --all')
if name == 'main': main()