Skip to content

Instantly share code, notes, and snippets.

@milkey-mouse
Created May 29, 2025 01:34
Show Gist options
  • Save milkey-mouse/9b0d8326bf3737f3664825781a64d76e to your computer and use it in GitHub Desktop.
Save milkey-mouse/9b0d8326bf3737f3664825781a64d76e to your computer and use it in GitHub Desktop.
Convert Claude Code logs to Markdown
#!/usr/bin/env python3
from contextlib import suppress
from dataclasses import dataclass
import argparse
import json
import sys
@dataclass(slots=True, frozen=True)
class Block:
type: str
content: str
@classmethod
def from_json(cls, item):
with suppress(KeyError):
if item["type"] == "text":
return cls(item["type"], item["text"])
elif item["type"] == "thinking":
return cls(item["type"], item["thinking"])
@dataclass(slots=True, frozen=True)
class Message:
uuid: str
parent: str
role: str
blocks: list[Block]
@classmethod
def from_json(cls, data):
with suppress(KeyError):
if data["type"] in ("user", "assistant"):
msg = cls(uuid=data["uuid"], parent=data.get("parentUuid", ""), role=data["message"]["role"], blocks=[])
content = data["message"]["content"]
try:
for item in content:
if block := Block.from_json(item):
msg.blocks.append(block)
except TypeError:
msg.blocks.append(Block("text", content))
return msg
def main():
parser = argparse.ArgumentParser(description="Convert Claude Code logs to Markdown")
parser.add_argument("--user", dest="user", action="store_true", help="Show user messages (default)")
parser.add_argument("--no-user", dest="user", action="store_false", help="Hide user messages")
parser.add_argument("--assistant", dest="assistant", action="store_true", help="Show assistant messages (default)")
parser.add_argument("--no-assistant", dest="assistant", action="store_false", help="Hide assistant messages")
parser.add_argument("--thinking", dest="thinking", action="store_true", help="Show thinking blocks")
parser.add_argument("--no-thinking", dest="thinking", action="store_false", help="Hide thinking blocks (default)")
parser.set_defaults(thinking=False, user=True, assistant=True)
args = parser.parse_args()
messages = {}
latest_message = None
for line in sys.stdin:
if not line.strip():
continue
data = json.loads(line)
if msg := Message.from_json(data):
messages[msg.uuid] = msg
if msg.blocks:
latest_message = msg
if not latest_message:
sys.exit(1)
chain = [latest_message]
while chain[-1].parent in messages:
chain.append(messages[chain[-1].parent])
first = True
for msg in reversed(chain):
if msg.role == "user" and not args.user:
continue
if msg.role == "assistant" and not args.assistant:
continue
for block in msg.blocks:
if block.type == "thinking" and not args.thinking:
continue
if not first:
print()
first = False
if msg.role == "user":
for line in block.content.split("\n"):
print(f"> {line}")
elif block.type == "thinking":
print("<thinking>")
print(block.content)
print("</thinking>")
else:
print(block.content)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment