"""Render a Claude Code stream-json transcript as Markdown. Nothing is dropped or edited.""" import json import sys def fence(text, lang=""): ticks = "````" if "```" in text else "```" return f"{ticks}{lang}\n{text}\n{ticks}\n" def as_text(content): if isinstance(content, str): return content parts = [] for c in content or []: if c.get("type") == "text": parts.append(c["text"]) else: parts.append(json.dumps(c, ensure_ascii=False)) return "\n".join(parts) def main(path): out = ["# Agent transcript\n"] step = 0 with open(path) as f: for line in f: line = line.strip() if not line: continue ev = json.loads(line) kind = ev.get("type") if kind == "system" and ev.get("subtype") == "init": out.append(f"- model: `{ev.get('model')}`") out.append(f"- claude code: `{ev.get('claude_code_version', '?')}`") out.append(f"- tools: {', '.join('`'+t+'`' for t in ev.get('tools', []))}") servers = ", ".join(f"`{s['name']}` ({s['status']})" for s in ev.get("mcp_servers", [])) out.append(f"- mcp servers: {servers}\n") elif kind == "assistant": for c in ev["message"].get("content", []): if c["type"] == "text" and c["text"].strip(): out.append(f"### Agent\n\n{c['text'].strip()}\n") elif c["type"] == "tool_use": step += 1 args = json.dumps(c["input"], indent=2, ensure_ascii=False) out.append(f"### Step {step}: `{c['name']}`\n") out.append(fence(args, "json")) elif kind == "user": content = ev["message"].get("content", []) if isinstance(content, str): out.append(f"### User\n\n{content.strip()}\n") continue for c in content: if c.get("type") == "tool_result": label = "Result (error)" if c.get("is_error") else "Result" out.append(f"
{label}\n") out.append(fence(as_text(c.get("content")))) out.append("
\n") elif c.get("type") == "text": out.append(f"### User\n\n{c['text'].strip()}\n") elif kind == "result": out.append("## Result\n") for key in ("subtype", "num_turns", "duration_ms", "total_cost_usd", "is_error"): if key in ev: out.append(f"- {key}: `{ev[key]}`") out.append("") print("\n".join(out)) if __name__ == "__main__": main(sys.argv[1])