Loading...
Loading...
Convert and browse session transcripts as HTML or Markdown. Supports Claude Code JSONL logs (auto-saved to ~/.claude/projects/) and GitHub Copilot CLI JSONL logs (auto-saved to ~/.copilot/session-state/*/events.jsonl). Auto-detects log source based on available directories and file format. Supports viewing the current session, a specific session by ID, agent background task output files, or all project sessions with optional date-range filtering.
npx skill4agent add rysweet/amplihack transcript-viewer~/.claude/projects/~/.copilot/session-state/*/events.jsonlsrc/amplihack/hooks/launcher_detector.py# Primary: directory-based detection (most reliable)
if [[ -d "$HOME/.copilot/session-state" ]]; then
# Check if there are any sessions present
COPILOT_SESSIONS=$(ls -d "$HOME/.copilot/session-state/"*/ 2>/dev/null | wc -l)
CLAUDE_SESSIONS=$(ls "$HOME/.claude/projects/"*/*.jsonl 2>/dev/null | wc -l)
if [[ "$COPILOT_SESSIONS" -gt 0 && "$CLAUDE_SESSIONS" -eq 0 ]]; then
TOOL_CONTEXT="copilot"
DEFAULT_LOG_DIR="$HOME/.copilot/session-state"
elif [[ "$CLAUDE_SESSIONS" -gt 0 ]]; then
TOOL_CONTEXT="claude-code"
DEFAULT_LOG_DIR="$HOME/.claude/projects"
else
# Both dirs exist but empty — use env vars to decide
TOOL_CONTEXT="claude-code"
DEFAULT_LOG_DIR="$HOME/.claude/projects"
fi
elif [[ -d "$HOME/.claude/projects" ]]; then
TOOL_CONTEXT="claude-code"
DEFAULT_LOG_DIR="$HOME/.claude/projects"
# Fallback: env var detection (same vars as launcher_detector.py)
elif [[ -n "${CLAUDE_CODE_SESSION:-}${CLAUDE_SESSION_ID:-}${ANTHROPIC_API_KEY:-}" ]]; then
TOOL_CONTEXT="claude-code"
DEFAULT_LOG_DIR="$HOME/.claude/projects"
elif [[ -n "${GITHUB_COPILOT_TOKEN:-}${COPILOT_SESSION:-}" ]]; then
# Note: GITHUB_TOKEN is intentionally excluded — it's too generic and
# appears in non-Copilot CI contexts, causing false positives.
TOOL_CONTEXT="copilot"
DEFAULT_LOG_DIR="$HOME/.copilot/session-state"
else
# Default to claude-code — safe fallback (most users)
TOOL_CONTEXT="claude-code"
DEFAULT_LOG_DIR="$HOME/.claude/projects"
fi~/.copilot/session-state/~/.claude/projects/detect_log_format() {
local file="$1"
if [[ "$file" == *.jsonl ]]; then
echo "jsonl"
elif [[ "$file" == *.md ]]; then
# Check for Copilot /share export signature — specific header only
# Note: do NOT match on "/share" alone (too generic — appears in docs, READMEs)
if grep -q "Copilot Session Export\|copilot-session" "$file" 2>/dev/null; then
echo "copilot-markdown"
else
echo "markdown"
fi
elif [[ "$file" == *.log ]]; then
# .log files may be plain text or agent JSONL; check content
local first_char
first_char=$(head -c 1 "$file" 2>/dev/null)
if [[ "$first_char" == "{" ]]; then
echo "jsonl"
else
echo "plain-log"
fi
else
# Inspect first byte for other extensions
local first_char
first_char=$(head -c 1 "$file" 2>/dev/null)
if [[ "$first_char" == "{" ]]; then
echo "jsonl"
else
echo "unknown"
fi
fi
}| Format | Handler |
|---|---|
| Pass to |
| Display inline (already readable Markdown) |
| Display inline |
| Warn and display raw |
claude-code-log# Step 1: direct install check
which claude-code-log 2>/dev/null
# Step 2: npx fallback
npx --yes claude-code-log --version 2>/dev/nullCCLif which claude-code-log &>/dev/null; then
CCL="claude-code-log"
elif npx --yes claude-code-log --version &>/dev/null 2>&1; then
CCL="npx claude-code-log"
else
CCL=""
fiCCLclaude-code-log is not installed.
To install it globally:
npm install -g claude-code-log
Or run without installing (requires npx):
npx claude-code-log --help
After installing, retry your request.TOOL_CONTEXT="claude-code"~/.claude/projects/ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1$CCL <path-to-jsonl> --format markdownTOOL_CONTEXT="copilot"~/.copilot/session-state/ls -dt ~/.copilot/session-state/*/ 2>/dev/null | head -1events.jsonlLATEST_SESSION=$(ls -dt ~/.copilot/session-state/*/ 2>/dev/null | head -1)
$CCL "${LATEST_SESSION}events.jsonl" --format markdown# Session: 2025-11-23 19:32
**Model**: claude-sonnet-4-6
**Messages**: 42
---
**User**: Fix the authentication bug in login.py
**Assistant**: I'll examine the file...
...TOOL_CONTEXT="claude-code"find ~/.claude/projects -name "*.jsonl" | xargs grep -l "<SESSION_ID>" 2>/dev/null | head -1ls ~/.claude/projects/*/ | grep "<SESSION_ID>"$CCL <path-to-jsonl> --format markdownNo session found with ID: <SESSION_ID>
Available sessions: run "view all sessions" to list them.TOOL_CONTEXT="copilot"~/.copilot/session-state/SESSION_DIR="$HOME/.copilot/session-state/<SESSION_ID>"
if [[ -d "$SESSION_DIR" ]]; then
EVENTS_FILE="$SESSION_DIR/events.jsonl"
fils -d ~/.copilot/session-state/*/ 2>/dev/null | grep "<SESSION_ID>"$CCL "$EVENTS_FILE" --format markdownNo Copilot session found with ID: <SESSION_ID>
Available sessions: run "browse all sessions" to list them..log.jsonl.agent-step-*.logls -t .agent-step-*.log 2>/dev/null
ls -t /tmp/*.agent*.log 2>/dev/nulldetect_log_format <file>jsonl$CCL <file> --format markdownplain-logcat.logNo agent background task output files found in the current directory.
Background agents write their output to files named .agent-step-<ID>.log.TOOL_CONTEXT="claude-code"~/.claude/projects/find ~/.claude/projects -name "*.jsonl" -printf "%T@ %p\n" 2>/dev/null \
| sort -rn | awk '{print $2}'$CCL <file> --format markdown --summary
# If --summary flag is not supported, just show filename and date
head -1 <file> | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('timestamp',''))"Available Sessions (Claude Code)
=================================
# Date Session ID / File
1 2025-11-23 19:32:36 ~/.claude/projects/foo/abc123.jsonl
2 2025-11-22 14:10:05 ~/.claude/projects/foo/def456.jsonl
...TOOL_CONTEXT="copilot"~/.copilot/session-state/ls -dt ~/.copilot/session-state/*/ 2>/dev/nullevents.jsonlfor session_dir in $(ls -dt ~/.copilot/session-state/*/); do
session_id=$(basename "$session_dir")
events_file="$session_dir/events.jsonl"
if [[ -f "$events_file" ]]; then
timestamp=$(head -1 "$events_file" | python3 -c \
"import sys,json; d=json.loads(sys.stdin.read()); print(d.get('timestamp',''))" 2>/dev/null)
echo "$timestamp $session_id"
fi
doneAvailable Sessions (GitHub Copilot CLI)
========================================
# Date Session ID
1 2025-11-23 19:32:36 abc1234567890abcdef1234567890abcd
2 2025-11-22 14:10:05 def4567890abcdef1234567890abcdef
...# Filter by modification time (last N days)
find ~/.claude/projects -name "*.jsonl" -mtime -7
# Filter by date range using find -newer
find ~/.claude/projects -name "*.jsonl" \
-newer /tmp/start_date_ref \
! -newer /tmp/end_date_reftouch -dtouch -d "2025-11-01" /tmp/start_date_ref
touch -d "2025-11-30" /tmp/end_date_ref--format markdownclaude-code-log--format htmlclaude-code-log$CCL <file> --format html > /tmp/transcript-view.html
open /tmp/transcript-view.html 2>/dev/null \
|| xdg-open /tmp/transcript-view.html 2>/dev/null \
|| echo "HTML saved to /tmp/transcript-view.html — open it in your browser."~/.copilot/session-state/{session-id}/events.jsonlworkspace.yamlplan.mdcheckpoints/~/.copilot/history-session-state/events.jsonl{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Fix the bug"}]},"timestamp":"2025-11-23T19:32:36Z","sessionId":"abc1234567890"}
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"I'll examine the file..."}]},"timestamp":"2025-11-23T19:32:40Z","sessionId":"abc1234567890"}claude-code-logevents.jsonl# Current session (most recent)
LATEST_SESSION=$(ls -dt ~/.copilot/session-state/*/ 2>/dev/null | head -1)
EVENTS_FILE="${LATEST_SESSION}events.jsonl"
# Specific session by ID
EVENTS_FILE="$HOME/.copilot/session-state/<SESSION_ID>/events.jsonl"$CCL "$EVENTS_FILE" --format markdownif [[ -d "$HOME/.copilot/session-state" ]]; then
SESSION_COUNT=$(ls -d "$HOME/.copilot/session-state/"*/ 2>/dev/null | wc -l)
if [[ "$SESSION_COUNT" -gt 0 ]]; then
echo "Found $SESSION_COUNT Copilot session(s)"
fi
fi0. Detect tool context (directory-based first, then env var fallback):
- ~/.copilot/session-state/ exists with sessions → copilot
- ~/.claude/projects/ exists with sessions → claude-code
- Both exist with sessions → offer user a choice
- Directory detection fails, use env vars:
- CLAUDE_CODE_SESSION / CLAUDE_SESSION_ID / ANTHROPIC_API_KEY set → claude-code
- GITHUB_COPILOT_TOKEN / COPILOT_SESSION set → copilot
- Neither → default to claude-code (safe fallback)
1. Detect CCL (which claude-code-log / npx fallback)
→ If missing: show install instructions and STOP
→ Required for ALL modes (both Claude Code and Copilot use JSONL format)
2. If user provides an explicit file path:
a. Detect its format (detect_log_format function above)
b. jsonl or events.jsonl → pass to $CCL
c. unknown → warn and display raw
3. Determine mode from user message:
- mentions "current" or no session specified → Mode 1 (Current Session)
- mentions a session ID or hash → Mode 2 (Specific Session)
- mentions "agent" or "background" → Mode 3 (Agent Output)
- mentions "all", "browse", "list" → Mode 4 (All Sessions)
4. Determine output format:
- "as HTML" or "export HTML" → html
- default → markdown
5. Execute the appropriate mode (using TOOL_CONTEXT to choose correct paths) and display results.| Situation | Response |
|---|---|
| Show install instructions, stop |
| JSONL file not found | "No session file found at <path>" |
| Session ID not found | "No session with ID <ID>. Run 'browse all sessions' to list available ones." |
| No agent output files | "No agent background task output found in current directory." |
| Empty JSONL file | "Session file is empty — no messages to display." |
| Date range produces no results | "No sessions found between <start> and <end>." |
| Display stderr and suggest |
| "No Copilot sessions found. Start a session with GitHub Copilot CLI to create logs." |
| Copilot session dir exists but no events.jsonl | "Session directory found but events.jsonl is missing at <path>" |
| Unknown file format | Warn user and display raw content |
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]},"timestamp":"...","sessionId":"..."}
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"..."}]},"timestamp":"..."}claude-code-logevents.jsonl~/.copilot/session-state/{session-id}/events.jsonl{"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]},"timestamp":"...","sessionId":"..."}
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"..."}]},"timestamp":"...","sessionId":"..."}claude-code-logworkspace.yamlplan.mdcheckpoints/~/.copilot/history-session-state/claude-code-logclaude-code-lognpx--summaryclaude-code-log~/.copilot/history-session-state/| User says | Tool | Mode | Command |
|---|---|---|---|
| "view current transcript" | Claude Code | Current session | |
| "show session abc123" | Claude Code | Specific session | |
| "view agent output" | Claude Code | Agent output | |
| "browse all sessions" | Claude Code | All sessions | list + summarize all |
| "view transcript as HTML" | Claude Code | Any + HTML | |
| "last 7 days" (with browse) | Claude Code | Date filter | |
| "view current copilot session" | Copilot | Current session | |
| "show copilot session abc123" | Copilot | Specific session | |
| "browse copilot sessions" | Copilot | All sessions | list dirs in |
| "view copilot session as HTML" | Copilot | Any + HTML | |