Created
January 13, 2026 03:48
-
-
Save phact/cddcffc229f259351e808ac45c3ad4d4 to your computer and use it in GitHub Desktop.
Claude session: 4f118ac2-6a8b-47c7-a743-5eaa85a4f81a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 3: "esc esc rewind - lost history" | |
| # Shows the pain of losing rewound sessions | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: The rewind moment | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| # In the middle of debugging auth... | |
| Claude: I've identified the issue. The JWT validation is | |
| failing because the token expiry check uses UTC | |
| but your server is configured for local time. | |
| Let me also refactor the middleware to-- | |
| > *esc esc* # oops, running low on context | |
| /rewind | |
| Rewinding to message 23 of 47... | |
| EOF | |
| # Frame 2: Later, needing that context | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| # 3 days later, similar bug appears... | |
| $ # What was that timezone thing Claude explained? | |
| $ # Which session was it? | |
| $ # What was the fix? | |
| $ ls ~/.claude/projects/api-gateway/sessions/ | |
| 01JFGH... 01JFGI... 01JFGJ... 01JFGK... | |
| 01JFGL... 01JFGM... 01JFGN... 01JFGO... | |
| 01JFGP... 01JFGQ... 01JFGR... 01JFGS... | |
| # ... 47 ULIDs staring back at you | |
| EOF | |
| # Frame 3: The futile search | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03.png <<'EOF' | |
| $ # Fine, I'll grep for it | |
| $ grep -r "timezone" ~/.claude/projects/api-gateway/sessions/ | |
| # 2,847 matches across 31 sessions | |
| $ # Maybe the session ID? | |
| $ claude -r 01JFGK... # wrong one | |
| $ claude -r 01JFGL... # also wrong | |
| $ claude -r 01JFGM... # nope | |
| $ # I'll just figure it out again... | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 03_lost_rewinds.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 03_lost_rewinds.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 3: "esc esc rewind - lost history" | |
| # Shows the pain of losing rewound sessions | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: The rewind moment | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| # In the middle of debugging auth... | |
| Claude: I've identified the issue. The JWT validation is | |
| failing because the token expiry check uses UTC | |
| but your server is configured for local time. | |
| Let me also refactor the middleware to-- | |
| > *esc esc* # oops, running low on context | |
| /rewind | |
| Rewinding to message 23 of 47... | |
| EOF | |
| # Frame 2: Later, needing that context | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| # 3 days later, similar bug appears... | |
| $ # What was that timezone thing Claude explained? | |
| $ # Which session was it? | |
| $ # What was the fix? | |
| $ ls ~/.claude/projects/api-gateway/sessions/ | |
| 01JFGH... 01JFGI... 01JFGJ... 01JFGK... | |
| 01JFGL... 01JFGM... 01JFGN... 01JFGO... | |
| 01JFGP... 01JFGQ... 01JFGR... 01JFGS... | |
| # ... 47 ULIDs staring back at you | |
| EOF | |
| # Frame 3: The futile search | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03.png <<'EOF' | |
| $ # Fine, I'll grep for it | |
| $ grep -r "timezone" ~/.claude/projects/api-gateway/sessions/ | |
| # 2,847 matches across 31 sessions | |
| $ # Maybe the session ID? | |
| $ claude -r 01JFGK... # wrong one | |
| $ claude -r 01JFGL... # also wrong | |
| $ claude -r 01JFGM... # nope | |
| $ # I'll just figure it out again... | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 03_lost_rewinds.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 03_lost_rewinds.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 3: "esc esc rewind - lost history" | |
| # Shows the pain of losing rewound sessions | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: The rewind moment | |
| cat > /tmp/frame1.txt <<'EOF' | |
| # In the middle of debugging auth... | |
| Claude: I've identified the issue. The JWT validation | |
| is failing because the token expiry check uses | |
| UTC but your server is configured for local time. | |
| Let me also refactor the middleware to-- | |
| > *esc esc* # oops, running low on context | |
| /rewind | |
| Rewinding to message 23 of 47... | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: Later, needing that context | |
| cat > /tmp/frame2.txt <<'EOF' | |
| # 3 days later, similar bug appears... | |
| $ # What was that timezone thing Claude explained? | |
| $ # Which session was it? | |
| $ # What was the fix? | |
| $ ls ~/.claude/projects/api-gateway/sessions/ | |
| 01JFGH... 01JFGI... 01JFGJ... 01JFGK... | |
| 01JFGL... 01JFGM... 01JFGN... 01JFGO... | |
| 01JFGP... 01JFGQ... 01JFGR... 01JFGS... | |
| # ... 47 ULIDs staring back at you | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The futile search | |
| cat > /tmp/frame3.txt <<'EOF' | |
| $ # Fine, I'll grep for it | |
| $ grep -r "timezone" ~/.claude/projects/api-gateway/ | |
| # 2,847 matches across 31 sessions | |
| $ # Maybe just try session IDs? | |
| $ claude -r 01JFGK... # wrong one | |
| $ claude -r 01JFGL... # also wrong | |
| $ claude -r 01JFGM... # nope | |
| $ # I'll just figure it out again... | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 03_lost_rewinds.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 03_lost_rewinds.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 3: "esc esc rewind - lost history" | |
| # Shows the pain of losing rewound sessions | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#0d1117" | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: The rewind moment | |
| cat > /tmp/frame1.txt <<'EOF' | |
| # In the middle of debugging auth... | |
| Claude: I've identified the issue. The JWT validation | |
| is failing because the token expiry check uses | |
| UTC but your server is configured for local time. | |
| Let me also refactor the middleware to-- | |
| > *esc esc* # oops, running low on context | |
| /rewind | |
| Rewinding to message 23 of 47... | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: Later, needing that context | |
| cat > /tmp/frame2.txt <<'EOF' | |
| # 3 days later, similar bug appears... | |
| $ # What was that timezone thing Claude explained? | |
| $ # Which session was it? | |
| $ # What was the fix? | |
| $ ls ~/.claude/projects/api-gateway/sessions/ | |
| 01JFGH... 01JFGI... 01JFGJ... 01JFGK... | |
| 01JFGL... 01JFGM... 01JFGN... 01JFGO... | |
| 01JFGP... 01JFGQ... 01JFGR... 01JFGS... | |
| # ... 47 ULIDs staring back at you | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The futile search | |
| cat > /tmp/frame3.txt <<'EOF' | |
| $ # Fine, I'll grep for it | |
| $ grep -r "timezone" ~/.claude/projects/api-gateway/ | |
| # 2,847 matches across 31 sessions | |
| $ # Maybe just try session IDs? | |
| $ claude -r 01JFGK... # wrong one | |
| $ claude -r 01JFGL... # also wrong | |
| $ claude -r 01JFGM... # nope | |
| $ # I'll just figure it out again... | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 03_lost_rewinds.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 03_lost_rewinds.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 1: "Too many Claudes running around" | |
| # Uses silicon to create frames, ffmpeg to animate | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: tmux ls showing many sessions | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| $ tmux ls | |
| api-refactor: 3 windows (created Mon Jan 6 09:14:22 2025) | |
| auth-fix: 2 windows (created Mon Jan 6 11:32:45 2025) | |
| bug-1847: 1 windows (created Tue Jan 7 14:22:11 2025) | |
| claude-review: 2 windows (created Tue Jan 7 16:45:33 2025) | |
| feature-export: 4 windows (created Wed Jan 8 08:12:19 2025) | |
| hotfix-prod: 1 windows (created Wed Jan 8 22:01:44 2025) | |
| main-dev: 5 windows (created Thu Jan 9 07:30:00 2025) | |
| perf-testing: 2 windows (created Thu Jan 9 15:18:27 2025) | |
| pr-review-423: 1 windows (created Fri Jan 10 10:45:12 2025) | |
| EOF | |
| # Frame 2: Multiple projects in ~/.claude | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| $ ls ~/.claude/projects/ | head -15 | |
| -rw-r--r-- api-gateway/ | |
| -rw-r--r-- billing-service/ | |
| -rw-r--r-- claude-mcp-server/ | |
| -rw-r--r-- data-pipeline/ | |
| -rw-r--r-- frontend-v2/ | |
| -rw-r--r-- internal-tools/ | |
| -rw-r--r-- ml-inference/ | |
| -rw-r--r-- mobile-app/ | |
| -rw-r--r-- one_claude/ | |
| -rw-r--r-- payment-processor/ | |
| -rw-r--r-- search-indexer/ | |
| -rw-r--r-- user-service/ | |
| ... and 23 more | |
| EOF | |
| # Frame 3: The existential question | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03.png <<'EOF' | |
| $ # which terminal had my auth fix? | |
| $ # was it the laptop or the desktop? | |
| $ # did I /rewind that session? | |
| $ # what was the session id again? | |
| $ claude -r ??? | |
| EOF | |
| # Duplicate frames for timing (1.5s per frame at 10fps = 15 copies) | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette for better GIF quality | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 01_too_many_claudes.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 01_too_many_claudes.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 1: "Too many Claudes running around" | |
| # Uses silicon to create frames, ffmpeg to animate | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: tmux ls showing many sessions | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ tmux ls | |
| api-refactor: 3 windows (created Mon Jan 6 09:14:22 2025) | |
| auth-fix: 2 windows (created Mon Jan 6 11:32:45 2025) | |
| bug-1847: 1 windows (created Tue Jan 7 14:22:11 2025) | |
| claude-review: 2 windows (created Tue Jan 7 16:45:33 2025) | |
| feature-export: 4 windows (created Wed Jan 8 08:12:19 2025) | |
| hotfix-prod: 1 windows (created Wed Jan 8 22:01:44 2025) | |
| main-dev: 5 windows (created Thu Jan 9 07:30:00 2025) | |
| perf-testing: 2 windows (created Thu Jan 9 15:18:27 2025) | |
| pr-review-423: 1 windows (created Fri Jan 10 10:45:12 2025) | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: Multiple projects in ~/.claude | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/ | head -15 | |
| api-gateway/ | |
| billing-service/ | |
| claude-mcp-server/ | |
| data-pipeline/ | |
| frontend-v2/ | |
| internal-tools/ | |
| ml-inference/ | |
| mobile-app/ | |
| one_claude/ | |
| payment-processor/ | |
| search-indexer/ | |
| user-service/ | |
| ... and 23 more | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The existential question | |
| cat > /tmp/frame3.txt <<'EOF' | |
| $ # which terminal had my auth fix? | |
| $ # was it the laptop or the desktop? | |
| $ # did I /rewind that session? | |
| $ # what was the session id again? | |
| $ claude -r ??? | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Find max dimensions | |
| MAX_W=0 | |
| MAX_H=0 | |
| for f in frame_*_raw.png; do | |
| dims=$(identify -format '%wx%h' "$f") | |
| w=${dims%x*} | |
| h=${dims#*x} | |
| [ "$w" -gt "$MAX_W" ] && MAX_W=$w | |
| [ "$h" -gt "$MAX_H" ] && MAX_H=$h | |
| done | |
| echo "Normalizing to ${MAX_W}x${MAX_H}" | |
| # Pad all frames to max dimensions (centered, with background color) | |
| for i in 1 2 3; do | |
| convert "frame_0${i}_raw.png" \ | |
| -gravity center \ | |
| -background "$BG" \ | |
| -extent "${MAX_W}x${MAX_H}" \ | |
| "frame_0${i}.png" | |
| done | |
| # Duplicate frames for timing (1.5s per frame at 10fps = 15 copies) | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF directly (simpler approach) | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 01_too_many_claudes.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 01_too_many_claudes.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 1: "Too many Claudes running around" | |
| # Uses silicon to create frames, ffmpeg to animate | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Target size (will pad smaller frames to this) | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: tmux ls showing many sessions | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ tmux ls | |
| api-refactor: 3 windows (created Mon Jan 6 09:14:22 2025) | |
| auth-fix: 2 windows (created Mon Jan 6 11:32:45 2025) | |
| bug-1847: 1 windows (created Tue Jan 7 14:22:11 2025) | |
| claude-review: 2 windows (created Tue Jan 7 16:45:33 2025) | |
| feature-export: 4 windows (created Wed Jan 8 08:12:19 2025) | |
| hotfix-prod: 1 windows (created Wed Jan 8 22:01:44 2025) | |
| main-dev: 5 windows (created Thu Jan 9 07:30:00 2025) | |
| perf-testing: 2 windows (created Thu Jan 9 15:18:27 2025) | |
| pr-review-423: 1 windows (created Fri Jan 10 10:45:12 2025) | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: Multiple projects in ~/.claude | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/ | head -15 | |
| api-gateway/ | |
| billing-service/ | |
| claude-mcp-server/ | |
| data-pipeline/ | |
| frontend-v2/ | |
| internal-tools/ | |
| ml-inference/ | |
| mobile-app/ | |
| one_claude/ | |
| payment-processor/ | |
| search-indexer/ | |
| user-service/ | |
| ... and 23 more | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The existential question | |
| cat > /tmp/frame3.txt <<'EOF' | |
| $ # which terminal had my auth fix? | |
| $ # was it the laptop or the desktop? | |
| $ # did I /rewind that session? | |
| $ # what was the session id again? | |
| $ claude -r ??? | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size using ffmpeg (pad with bg color, centered) | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing (1.5s per frame at 10fps = 15 copies) | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF with palette optimization | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 01_too_many_claudes.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 01_too_many_claudes.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 1: "Too many Claudes running around" | |
| # Uses silicon to create frames, ffmpeg to animate | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#0d1117" | |
| # Target size (will pad smaller frames to this) | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: tmux ls showing many sessions | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ tmux ls | |
| api-refactor: 3 windows (created Mon Jan 6 09:14:22 2025) | |
| auth-fix: 2 windows (created Mon Jan 6 11:32:45 2025) | |
| bug-1847: 1 windows (created Tue Jan 7 14:22:11 2025) | |
| claude-review: 2 windows (created Tue Jan 7 16:45:33 2025) | |
| feature-export: 4 windows (created Wed Jan 8 08:12:19 2025) | |
| hotfix-prod: 1 windows (created Wed Jan 8 22:01:44 2025) | |
| main-dev: 5 windows (created Thu Jan 9 07:30:00 2025) | |
| perf-testing: 2 windows (created Thu Jan 9 15:18:27 2025) | |
| pr-review-423: 1 windows (created Fri Jan 10 10:45:12 2025) | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: Multiple projects in ~/.claude | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/ | head -15 | |
| api-gateway/ | |
| billing-service/ | |
| claude-mcp-server/ | |
| data-pipeline/ | |
| frontend-v2/ | |
| internal-tools/ | |
| ml-inference/ | |
| mobile-app/ | |
| one_claude/ | |
| payment-processor/ | |
| search-indexer/ | |
| user-service/ | |
| ... and 23 more | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The existential question | |
| cat > /tmp/frame3.txt <<'EOF' | |
| $ # which terminal had my auth fix? | |
| $ # was it the laptop or the desktop? | |
| $ # did I /rewind that session? | |
| $ # what was the session id again? | |
| $ claude -r ??? | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size using ffmpeg (pad with bg color, centered) | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing (1.5s per frame at 10fps = 15 copies) | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF with palette optimization | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 01_too_many_claudes.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 01_too_many_claudes.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Generate all images for the tweet thread | |
| # | |
| # Requirements: | |
| # - silicon: cargo install silicon | |
| # - vhs: brew install vhs (or go install github.com/charmbracelet/vhs@latest) | |
| # - ffmpeg: apt install ffmpeg / brew install ffmpeg | |
| # | |
| # Usage: | |
| # ./generate_all.sh # Generate all | |
| # ./generate_all.sh silicon # Only silicon/ffmpeg GIFs | |
| # ./generate_all.sh vhs # Only VHS recordings | |
| set -e | |
| cd "$(dirname "$0")" | |
| generate_silicon() { | |
| echo "=== Generating silicon + ffmpeg GIFs ===" | |
| for script in 01_too_many_claudes.sh 02_varnishing_context.sh 03_lost_rewinds.sh; do | |
| echo "Running $script..." | |
| bash "$script" | |
| done | |
| } | |
| generate_vhs() { | |
| echo "=== Generating VHS recordings ===" | |
| for tape in 04_tui_tree.tape 05_teleport.tape 06_hero.tape; do | |
| echo "Recording $tape..." | |
| vhs "$tape" | |
| done | |
| } | |
| case "${1:-all}" in | |
| silicon) | |
| generate_silicon | |
| ;; | |
| vhs) | |
| generate_vhs | |
| ;; | |
| all) | |
| generate_silicon | |
| generate_vhs | |
| ;; | |
| *) | |
| echo "Usage: $0 [silicon|vhs|all]" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "" | |
| echo "=== Done! Generated GIFs: ===" | |
| ls -la *.gif 2>/dev/null || echo "No GIFs found" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Generate all images for the tweet thread | |
| # | |
| # Requirements: | |
| # - silicon: cargo install silicon | |
| # - vhs: brew install vhs (or go install github.com/charmbracelet/vhs@latest) | |
| # - ffmpeg: apt install ffmpeg / brew install ffmpeg | |
| # | |
| # Usage: | |
| # ./generate_all.sh # Generate all | |
| # ./generate_all.sh silicon # Only silicon/ffmpeg GIFs | |
| # ./generate_all.sh vhs # Only VHS recordings | |
| set -e | |
| cd "$(dirname "$0")" | |
| generate_silicon() { | |
| echo "=== Generating silicon + ffmpeg GIFs ===" | |
| for script in 01_too_many_claudes.sh 02_varnishing_context.sh 03_lost_rewinds.sh 07_meta_closing.sh; do | |
| echo "Running $script..." | |
| bash "$script" | |
| done | |
| } | |
| generate_vhs() { | |
| echo "=== Generating VHS recordings ===" | |
| for tape in 00_meta_teleport.tape 04_tui_tree.tape 05_teleport.tape 06_hero.tape; do | |
| echo "Recording $tape..." | |
| vhs "$tape" | |
| done | |
| } | |
| case "${1:-all}" in | |
| silicon) | |
| generate_silicon | |
| ;; | |
| vhs) | |
| generate_vhs | |
| ;; | |
| all) | |
| generate_silicon | |
| generate_vhs | |
| ;; | |
| *) | |
| echo "Usage: $0 [silicon|vhs|all]" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "" | |
| echo "=== Done! Generated GIFs: ===" | |
| ls -la *.gif 2>/dev/null || echo "No GIFs found" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 0 or 7: Meta - "This thread was made with one_claude" | |
| # Shows teleporting into THIS VERY CONVERSATION | |
| # Use as opening hook or closing "inception" moment | |
| Output 00_meta_teleport.gif | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 2s | |
| # Search for this conversation | |
| Type "/" | |
| Sleep 400ms | |
| Type@80ms "tweet thread" | |
| Sleep 1s | |
| Enter | |
| Sleep 1s | |
| # It should find the session where we discussed the thread | |
| # Navigate to it | |
| Type "j" | |
| Sleep 300ms | |
| # Show it's this conversation | |
| Enter | |
| Sleep 2s | |
| # Back out | |
| Escape | |
| Sleep 500ms | |
| # Teleport into it | |
| Type "t" | |
| Sleep 3s | |
| # Now we're in the session - Claude on left, shell on right | |
| # The transcript shows our conversation about making this thread | |
| Sleep 3s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 0 or 7: Meta - "This thread was made with one_claude" | |
| # Shows teleporting into THIS VERY CONVERSATION | |
| # Use as opening hook or closing "inception" moment | |
| Output "00_meta_teleport.gif" | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 2s | |
| # Search for this conversation | |
| Type "/" | |
| Sleep 400ms | |
| Type@80ms "tweet thread" | |
| Sleep 1s | |
| Enter | |
| Sleep 1s | |
| # It should find the session where we discussed the thread | |
| # Navigate to it | |
| Type "j" | |
| Sleep 300ms | |
| # Show it's this conversation | |
| Enter | |
| Sleep 2s | |
| # Back out | |
| Escape | |
| Sleep 500ms | |
| # Teleport into it | |
| Type "t" | |
| Sleep 3s | |
| # Now we're in the session - Claude on left, shell on right | |
| # The transcript shows our conversation about making this thread | |
| Sleep 3s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 6: "One place - the hero shot" | |
| # Full demo of one_claude | |
| Output 06_hero.gif | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Start with the command | |
| Type "# one place to manage all your claudes" | |
| Enter | |
| Sleep 1s | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 3s | |
| # Quick navigation demo | |
| Type "j" | |
| Sleep 200ms | |
| Type "j" | |
| Sleep 200ms | |
| Type "j" | |
| Sleep 200ms | |
| Type "k" | |
| Sleep 200ms | |
| Type "k" | |
| Sleep 200ms | |
| # Page navigation | |
| Ctrl+d | |
| Sleep 500ms | |
| Ctrl+u | |
| Sleep 500ms | |
| # Search | |
| Type "/" | |
| Sleep 400ms | |
| Type@80ms "refactor" | |
| Sleep 800ms | |
| Enter | |
| Sleep 1s | |
| # Clear search | |
| Escape | |
| Sleep 500ms | |
| # Copy session ID | |
| Type "y" | |
| Sleep 1s | |
| # Show mode options | |
| Type "m" | |
| Sleep 800ms | |
| Type "m" | |
| Sleep 800ms | |
| # Jump to top | |
| Type "gg" | |
| Sleep 500ms | |
| # Select and view | |
| Enter | |
| Sleep 1s | |
| # Back | |
| Escape | |
| Sleep 500ms | |
| # Teleport teaser | |
| Type "t" | |
| Sleep 2s | |
| # End | |
| Type "q" | |
| Sleep 500ms |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 6: "One place - the hero shot" | |
| # Full demo of one_claude | |
| Output "06_hero.gif" | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Start with the command | |
| Type "# one place to manage all your claudes" | |
| Enter | |
| Sleep 1s | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 3s | |
| # Quick navigation demo | |
| Type "j" | |
| Sleep 200ms | |
| Type "j" | |
| Sleep 200ms | |
| Type "j" | |
| Sleep 200ms | |
| Type "k" | |
| Sleep 200ms | |
| Type "k" | |
| Sleep 200ms | |
| # Page navigation | |
| Ctrl+d | |
| Sleep 500ms | |
| Ctrl+u | |
| Sleep 500ms | |
| # Search | |
| Type "/" | |
| Sleep 400ms | |
| Type@80ms "refactor" | |
| Sleep 800ms | |
| Enter | |
| Sleep 1s | |
| # Clear search | |
| Escape | |
| Sleep 500ms | |
| # Copy session ID | |
| Type "y" | |
| Sleep 1s | |
| # Show mode options | |
| Type "m" | |
| Sleep 800ms | |
| Type "m" | |
| Sleep 800ms | |
| # Jump to top | |
| Type "gg" | |
| Sleep 500ms | |
| # Select and view | |
| Enter | |
| Sleep 1s | |
| # Back | |
| Escape | |
| Sleep 500ms | |
| # Teleport teaser | |
| Type "t" | |
| Sleep 2s | |
| # End | |
| Type "q" | |
| Sleep 500ms |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 4: "Git-log-graph-like UI" | |
| # Shows the TUI with session tree view | |
| Output 04_tui_tree.gif | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch the TUI | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 3s | |
| # Navigate to show the tree structure | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| # Expand a session to show branches | |
| Enter | |
| Sleep 1s | |
| # Navigate through branches | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| # Go back | |
| Escape | |
| Sleep 500ms | |
| # Show search | |
| Type "/" | |
| Sleep 500ms | |
| Type@100ms "auth" | |
| Sleep 1s | |
| Enter | |
| Sleep 1s | |
| # Clear and show more | |
| Escape | |
| Sleep 500ms | |
| # Navigate around | |
| Type "G" | |
| Sleep 1s | |
| Type "gg" | |
| Sleep 1s | |
| # Quit | |
| Type "q" | |
| Sleep 500ms |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 4: "Git-log-graph-like UI" | |
| # Shows the TUI with session tree view | |
| Output "04_tui_tree.gif" | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch the TUI | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 3s | |
| # Navigate to show the tree structure | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| # Expand a session to show branches | |
| Enter | |
| Sleep 1s | |
| # Navigate through branches | |
| Type "j" | |
| Sleep 400ms | |
| Type "j" | |
| Sleep 400ms | |
| # Go back | |
| Escape | |
| Sleep 500ms | |
| # Show search | |
| Type "/" | |
| Sleep 500ms | |
| Type@100ms "auth" | |
| Sleep 1s | |
| Enter | |
| Sleep 1s | |
| # Clear and show more | |
| Escape | |
| Sleep 500ms | |
| # Navigate around | |
| Type "G" | |
| Sleep 1s | |
| Type "gg" | |
| Sleep 1s | |
| # Quit | |
| Type "q" | |
| Sleep 500ms |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 5: "Teleport - local, docker, or microvm" | |
| # Shows teleporting a session | |
| Output 05_teleport.gif | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch the TUI | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 2s | |
| # Navigate to a session | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| # Show mode toggle | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| # Teleport! | |
| Type "t" | |
| Sleep 3s | |
| # The tmux split should appear with Claude on left, shell on right | |
| # Show some interaction | |
| Sleep 2s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 5: "Teleport - local, docker, or microvm" | |
| # Shows teleporting a session | |
| Output "05_teleport.gif" | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch the TUI | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 2s | |
| # Navigate to a session | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| # Show mode toggle | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| # Teleport! | |
| Type "t" | |
| Sleep 3s | |
| # The tmux split should appear with Claude on left, shell on right | |
| # Show some interaction | |
| Sleep 2s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tweet 5: "Teleport - local, docker, or microvm" | |
| # Shows teleporting a session | |
| Output "05_teleport.gif" | |
| Set FontSize 16 | |
| Set Width 1400 | |
| Set Height 900 | |
| Set Theme "Dracula" | |
| Set Padding 20 | |
| # Launch the TUI | |
| Type "uvx one_claude" | |
| Enter | |
| Sleep 2s | |
| # Navigate to a session | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| Type "j" | |
| Sleep 300ms | |
| # Show mode toggle | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| Type "m" | |
| Sleep 1s | |
| # Teleport! | |
| Type "t" | |
| Sleep 3s | |
| # The tmux split should appear with Claude on left, shell on right | |
| # Show some interaction | |
| Sleep 2s | |
| Enter | |
| Sleep 2s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 7: Meta closing - text version | |
| # "This thread was made in a session you can teleport to" | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: The reveal | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| $ # This tweet thread was written in a Claude session | |
| $ # You can teleport into it right now: | |
| $ uvx one_claude gist import <GIST_ID> | |
| EOF | |
| # Frame 2: What you get | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| Importing session from gist... | |
| ✓ Downloaded transcript (847 messages) | |
| ✓ Restored file checkpoints | |
| ✓ Rebuilt conversation tree | |
| Session ready. Teleporting... | |
| # tmux splits: Claude resumes on left, shell on right | |
| # Full context preserved. Continue where I left off. | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2; do | |
| for j in $(seq 1 20); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 07_meta_closing.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 07_meta_closing.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 7: Meta closing - text version | |
| # "This thread was made in a session you can teleport to" | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: The reveal | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| $ # This tweet thread was written in a Claude session | |
| $ # You can teleport into it right now: | |
| $ uvx one_claude gist import <GIST_ID> | |
| EOF | |
| # Frame 2: What you get | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| Importing session from gist... | |
| ✓ Downloaded transcript (847 messages) | |
| ✓ Restored file checkpoints | |
| ✓ Rebuilt conversation tree | |
| Session ready. Teleporting... | |
| # tmux splits: Claude resumes on left, shell on right | |
| # Full context preserved. Continue where I left off. | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2; do | |
| for j in $(seq 1 20); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 07_meta_closing.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 07_meta_closing.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 7: Meta closing - "This thread was made in a session you can teleport to" | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| TARGET_W=1200 | |
| TARGET_H=500 | |
| # Frame 1: The reveal | |
| cat > /tmp/frame1.txt <<'EOF' | |
| # This tweet thread was written in a Claude session | |
| # You can teleport into it right now: | |
| $ uvx one_claude gist import <GIST_ID> | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: What you get | |
| cat > /tmp/frame2.txt <<'EOF' | |
| Importing session from gist... | |
| [ok] Downloaded transcript (847 messages) | |
| [ok] Restored file checkpoints | |
| [ok] Rebuilt conversation tree | |
| Session ready. Teleporting... | |
| # tmux splits: Claude resumes left, shell right | |
| # Full context preserved. Continue where I left off. | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing (2s per frame at 10fps = 20 copies) | |
| for i in 1 2; do | |
| for j in $(seq 1 20); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 07_meta_closing.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 07_meta_closing.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 7: Meta closing - "This thread was made in a session you can teleport to" | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#0d1117" | |
| TARGET_W=1200 | |
| TARGET_H=500 | |
| # Frame 1: The reveal | |
| cat > /tmp/frame1.txt <<'EOF' | |
| # This tweet thread was written in a Claude session | |
| # You can teleport into it right now: | |
| $ uvx one_claude gist import <GIST_ID> | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: What you get | |
| cat > /tmp/frame2.txt <<'EOF' | |
| Importing session from gist... | |
| [ok] Downloaded transcript (847 messages) | |
| [ok] Restored file checkpoints | |
| [ok] Rebuilt conversation tree | |
| Session ready. Teleporting... | |
| # tmux splits: Claude resumes left, shell right | |
| # Full context preserved. Continue where I left off. | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing (2s per frame at 10fps = 20 copies) | |
| for i in 1 2; do | |
| for j in $(seq 1 20); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 07_meta_closing.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 07_meta_closing.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """CLI interface for one_claude.""" | |
| import click | |
| from one_claude.config import Config | |
| @click.group(invoke_without_command=True) | |
| @click.option("--config", "-c", type=click.Path(), help="Path to config file") | |
| @click.pass_context | |
| def main(ctx: click.Context, config: str | None) -> None: | |
| """one_claude - Time Travel for Claude Code Sessions. | |
| Browse, search, and teleport to your Claude Code sessions across time. | |
| """ | |
| ctx.ensure_object(dict) | |
| ctx.obj["config"] = Config.load(config) if config else Config.load() | |
| # If no subcommand, run the TUI | |
| if ctx.invoked_subcommand is None: | |
| from one_claude.tui.app import OneClaude | |
| app = OneClaude(ctx.obj["config"]) | |
| app.run() | |
| @main.command() | |
| @click.pass_context | |
| def sessions(ctx: click.Context) -> None: | |
| """List all sessions.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| table = Table(title="Claude Code Sessions") | |
| table.add_column("Session ID", style="dim") | |
| table.add_column("Project", style="cyan") | |
| table.add_column("Title", style="white") | |
| table.add_column("Messages", justify="right") | |
| table.add_column("Updated", style="green") | |
| sessions = scanner.get_sessions_flat() | |
| for session in sessions[:50]: # Limit to 50 | |
| project_name = session.project_display.rstrip("/").split("/")[-1] | |
| title = (session.title or "Untitled")[:40] | |
| updated = session.updated_at.strftime("%Y-%m-%d %H:%M") | |
| session_id = session.id[:12] + "..." | |
| table.add_row(session_id, project_name, title, str(session.message_count), updated) | |
| console.print(table) | |
| @main.command() | |
| @click.argument("session_id") | |
| @click.pass_context | |
| def show(ctx: click.Context, session_id: str) -> None: | |
| """Show a specific session.""" | |
| from rich.console import Console | |
| from rich.markdown import Markdown | |
| from rich.panel import Panel | |
| from one_claude.core.models import MessageType | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| # Find the session | |
| for project in scanner.scan_all(): | |
| for session in project.sessions: | |
| if session.id == session_id or session.id.startswith(session_id): | |
| # Load messages | |
| tree = scanner.load_session_messages(session) | |
| messages = tree.get_main_thread() | |
| console.print(f"\n[bold]{session.title}[/bold]") | |
| console.print(f"[dim]{session.project_display}[/dim]\n") | |
| for msg in messages: | |
| if msg.type == MessageType.USER: | |
| console.print(Panel(msg.text_content[:500], title="User")) | |
| elif msg.type == MessageType.ASSISTANT: | |
| content = msg.text_content[:500] | |
| if msg.tool_uses: | |
| tools = ", ".join(t.name for t in msg.tool_uses) | |
| content += f"\n[dim]Tools: {tools}[/dim]" | |
| console.print(Panel(content, title="Assistant")) | |
| return | |
| console.print(f"[red]Session not found: {session_id}[/red]") | |
| @main.command() | |
| @click.pass_context | |
| def projects(ctx: click.Context) -> None: | |
| """List all projects.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| table = Table(title="Projects") | |
| table.add_column("Path", style="cyan") | |
| table.add_column("Sessions", justify="right") | |
| table.add_column("Latest", style="green") | |
| projects = scanner.scan_all() | |
| for project in projects: | |
| latest = project.latest_session | |
| latest_date = latest.updated_at.strftime("%Y-%m-%d") if latest else "-" | |
| table.add_row(project.display_path, str(project.session_count), latest_date) | |
| console.print(table) | |
| @main.command() | |
| @click.argument("query") | |
| @click.option("--mode", "-m", default="text", help="Search mode: text, title, content") | |
| @click.option("--limit", "-l", default=20, help="Maximum results") | |
| @click.pass_context | |
| def search(ctx: click.Context, query: str, mode: str, limit: int) -> None: | |
| """Search sessions.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.index.search import SearchEngine | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| engine = SearchEngine(scanner) | |
| console = Console() | |
| results = engine.search(query, mode=mode, limit=limit) | |
| if not results: | |
| console.print(f"[yellow]No results for '{query}'[/yellow]") | |
| return | |
| table = Table(title=f"Search: {query}") | |
| table.add_column("Score", justify="right", style="cyan") | |
| table.add_column("Session", style="white") | |
| table.add_column("Snippet", style="dim") | |
| for result in results: | |
| title = result.session.title or result.session.id[:8] | |
| snippet = result.snippet[:60] if result.snippet else "" | |
| table.add_row(f"{result.score:.2f}", title[:40], snippet) | |
| console.print(table) | |
| @main.command() | |
| @click.pass_context | |
| def tui(ctx: click.Context) -> None: | |
| """Launch the interactive TUI.""" | |
| from one_claude.tui.app import OneClaude | |
| app = OneClaude(ctx.obj["config"]) | |
| app.run() | |
| @main.group() | |
| def gist() -> None: | |
| """Gist export/import commands.""" | |
| pass | |
| @gist.command(name="import") | |
| @click.argument("gist_url_or_id") | |
| @click.option("--teleport", "-t", is_flag=True, help="Teleport into the session after import") | |
| @click.option( | |
| "--mode", | |
| "-m", | |
| type=click.Choice(["local", "docker", "microvm"]), | |
| default="docker", | |
| help="Teleport mode: local, docker, or microvm (default: docker)", | |
| ) | |
| @click.option( | |
| "--path", | |
| "-p", | |
| type=click.Path(), | |
| default=None, | |
| help="Path to clone/import project to (default: prompt or cwd)", | |
| ) | |
| @click.pass_context | |
| def gist_import(ctx: click.Context, gist_url_or_id: str, teleport: bool, mode: str, path: str | None) -> None: | |
| """Import a session from a GitHub gist.""" | |
| import asyncio | |
| import os | |
| import subprocess | |
| from pathlib import Path | |
| from rich.console import Console | |
| from one_claude.gist.importer import ExportInfo, SessionImporter | |
| config = ctx.obj["config"] | |
| console = Console() | |
| def clone_repo(git_info: dict, dest_path: str) -> bool: | |
| """Clone git repo and checkout specific commit.""" | |
| remote = git_info.get("remote") | |
| commit = git_info.get("commit") | |
| if not remote: | |
| console.print("[red]No git remote in export[/red]") | |
| return False | |
| console.print(f"[cyan]Cloning {remote}...[/cyan]") | |
| result = subprocess.run( | |
| ["git", "clone", remote, dest_path], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| console.print(f"[red]Clone failed: {result.stderr}[/red]") | |
| return False | |
| if commit: | |
| console.print(f"[cyan]Checking out {commit[:8]}...[/cyan]") | |
| result = subprocess.run( | |
| ["git", "checkout", commit], | |
| cwd=dest_path, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| console.print(f"[yellow]Warning: Could not checkout {commit[:8]}[/yellow]") | |
| return True | |
| async def do_import(): | |
| importer = SessionImporter(config.claude_dir) | |
| # First fetch export info to check if we need to clone | |
| console.print("[dim]Fetching export info...[/dim]") | |
| info = await importer.fetch_export_info(gist_url_or_id) | |
| if isinstance(info, str): | |
| console.print(f"[red]Failed: {info}[/red]") | |
| return | |
| final_path = None | |
| restore_files = False | |
| # If custom path provided, always use it (clone/restore there) | |
| if path: | |
| final_path = os.path.abspath(path) | |
| if os.path.exists(final_path): | |
| console.print(f"[yellow]Path already exists:[/yellow] {final_path}") | |
| if not click.confirm("Use existing directory?"): | |
| return | |
| elif info.git_info and info.git_info.get("remote"): | |
| if not clone_repo(info.git_info, final_path): | |
| return | |
| restore_files = True | |
| else: | |
| restore_files = True # Create dir and restore from checkpoints | |
| # Check if project exists (only if no custom path provided) | |
| elif not info.project_exists: | |
| console.print(f"[yellow]Project directory not found:[/yellow] {info.project_path}") | |
| if info.git_info and info.git_info.get("remote"): | |
| # Prompt for clone path | |
| cwd = os.getcwd() | |
| repo_name = info.git_info["remote"].rstrip("/").split("/")[-1].replace(".git", "") | |
| default_path = os.path.join(cwd, repo_name) | |
| clone_path = click.prompt( | |
| "Clone to", | |
| default=default_path, | |
| type=click.Path(), | |
| ) | |
| clone_path = os.path.abspath(clone_path) | |
| if os.path.exists(clone_path): | |
| console.print(f"[yellow]Path already exists:[/yellow] {clone_path}") | |
| if not click.confirm("Use existing directory?"): | |
| return | |
| else: | |
| if not clone_repo(info.git_info, clone_path): | |
| return | |
| final_path = clone_path | |
| restore_files = True | |
| else: | |
| # No git info - prompt and restore files from checkpoints | |
| cwd = os.getcwd() | |
| dir_name = Path(info.project_path).name | |
| default_path = os.path.join(cwd, dir_name) | |
| final_path = click.prompt( | |
| "Create project at", | |
| default=default_path, | |
| type=click.Path(), | |
| ) | |
| final_path = os.path.abspath(final_path) | |
| restore_files = True | |
| # Do the import | |
| result = await importer.import_session(info, final_path, restore_files=restore_files) | |
| if result.success: | |
| if result.already_imported: | |
| console.print(f"[yellow]Already imported[/yellow]") | |
| console.print(f" Session: {result.session_id}") | |
| console.print(f" Project: {result.project_path}") | |
| else: | |
| console.print(f"[green]Imported successfully![/green]") | |
| console.print(f" Session: {result.session_id}") | |
| console.print(f" Project: {result.project_path}") | |
| console.print(f" Messages: {result.message_count}") | |
| console.print(f" Checkpoints: {result.checkpoint_count}") | |
| if result.files_restored: | |
| console.print(f" Files restored: {result.files_restored}") | |
| if teleport: | |
| console.print(f"\n[cyan]Teleporting into session (mode: {mode})...[/cyan]") | |
| await do_teleport(result.session_id, result.project_path, mode, console) | |
| else: | |
| console.print(f"[red]Import failed: {result.error}[/red]") | |
| async def do_teleport(session_id: str, project_path: str, mode: str, console: Console): | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.teleport.restore import FileRestorer | |
| scanner = ClaudeScanner(config.claude_dir) | |
| restorer = FileRestorer(scanner) | |
| # Find the imported session | |
| session = scanner.get_session_by_id(session_id) | |
| if not session: | |
| console.print(f"[red]Could not find imported session: {session_id}[/red]") | |
| return | |
| # Restore to sandbox (latest message) | |
| teleport_session = await restorer.restore_to_sandbox( | |
| session, | |
| message_uuid=None, # Latest | |
| mode=mode, | |
| ) | |
| sandbox = teleport_session.sandbox | |
| # Get shell command | |
| shell_cmd = sandbox.get_shell_command(term=os.environ.get("TERM")) | |
| # Run the teleport command | |
| try: | |
| subprocess.run(shell_cmd, cwd=sandbox.working_dir) | |
| finally: | |
| await sandbox.stop() | |
| asyncio.run(do_import()) | |
| @gist.command(name="export") | |
| @click.argument("session_id") | |
| @click.pass_context | |
| def gist_export(ctx: click.Context, session_id: str) -> None: | |
| """Export a session to a GitHub gist.""" | |
| import asyncio | |
| from rich.console import Console | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.gist.exporter import SessionExporter | |
| config = ctx.obj["config"] | |
| console = Console() | |
| scanner = ClaudeScanner(config.claude_dir) | |
| # Find conversation path by session ID | |
| tree_cache = {} | |
| paths = scanner.scan_conversation_paths(tree_cache=tree_cache) | |
| target_path = None | |
| for path in paths: | |
| if path.id == session_id or path.id.startswith(session_id): | |
| target_path = path | |
| break | |
| for jsonl_file in path.jsonl_files: | |
| if jsonl_file.stem == session_id or jsonl_file.stem.startswith(session_id): | |
| target_path = path | |
| break | |
| if not target_path: | |
| console.print(f"[red]Session not found: {session_id}[/red]") | |
| return | |
| async def do_export(): | |
| exporter = SessionExporter(scanner) | |
| result = await exporter.export_full_session(target_path) | |
| if result.success: | |
| console.print(f"[green]Exported successfully![/green]") | |
| console.print(f" URL: {result.gist_url}") | |
| console.print(f" Messages: {result.message_count}") | |
| console.print(f" Checkpoints: {result.checkpoint_count}") | |
| else: | |
| console.print(f"[red]Export failed: {result.error}[/red]") | |
| asyncio.run(do_export()) | |
| @gist.command(name="list") | |
| def gist_list() -> None: | |
| """List exported gists.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.gist.store import load_exports | |
| console = Console() | |
| exports = load_exports() | |
| if not exports: | |
| console.print("[yellow]No exported gists yet[/yellow]") | |
| return | |
| table = Table(title="Exported Gists") | |
| table.add_column("Title", style="white") | |
| table.add_column("Messages", justify="right") | |
| table.add_column("Date", style="green") | |
| table.add_column("URL", style="cyan") | |
| for export in exports: | |
| table.add_row( | |
| export.title[:30], | |
| str(export.message_count), | |
| export.exported_at[:10], | |
| export.gist_url, | |
| ) | |
| console.print(table) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """CLI interface for one_claude.""" | |
| import click | |
| from one_claude.config import Config | |
| @click.group(invoke_without_command=True) | |
| @click.option("--config", "-c", type=click.Path(), help="Path to config file") | |
| @click.pass_context | |
| def main(ctx: click.Context, config: str | None) -> None: | |
| """one_claude - Time Travel for Claude Code Sessions. | |
| Browse, search, and teleport to your Claude Code sessions across time. | |
| """ | |
| ctx.ensure_object(dict) | |
| ctx.obj["config"] = Config.load(config) if config else Config.load() | |
| # If no subcommand, run the TUI | |
| if ctx.invoked_subcommand is None: | |
| from one_claude.tui.app import OneClaude | |
| app = OneClaude(ctx.obj["config"]) | |
| app.run() | |
| @main.command() | |
| @click.pass_context | |
| def sessions(ctx: click.Context) -> None: | |
| """List all sessions.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| table = Table(title="Claude Code Sessions") | |
| table.add_column("Session ID", style="dim") | |
| table.add_column("Project", style="cyan") | |
| table.add_column("Title", style="white") | |
| table.add_column("Messages", justify="right") | |
| table.add_column("Updated", style="green") | |
| sessions = scanner.get_sessions_flat() | |
| for session in sessions[:50]: # Limit to 50 | |
| project_name = session.project_display.rstrip("/").split("/")[-1] | |
| title = (session.title or "Untitled")[:40] | |
| updated = session.updated_at.strftime("%Y-%m-%d %H:%M") | |
| session_id = session.id[:12] + "..." | |
| table.add_row(session_id, project_name, title, str(session.message_count), updated) | |
| console.print(table) | |
| @main.command() | |
| @click.argument("session_id") | |
| @click.pass_context | |
| def show(ctx: click.Context, session_id: str) -> None: | |
| """Show a specific session.""" | |
| from rich.console import Console | |
| from rich.markdown import Markdown | |
| from rich.panel import Panel | |
| from one_claude.core.models import MessageType | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| # Find the session | |
| for project in scanner.scan_all(): | |
| for session in project.sessions: | |
| if session.id == session_id or session.id.startswith(session_id): | |
| # Load messages | |
| tree = scanner.load_session_messages(session) | |
| messages = tree.get_main_thread() | |
| console.print(f"\n[bold]{session.title}[/bold]") | |
| console.print(f"[dim]{session.project_display}[/dim]\n") | |
| for msg in messages: | |
| if msg.type == MessageType.USER: | |
| console.print(Panel(msg.text_content[:500], title="User")) | |
| elif msg.type == MessageType.ASSISTANT: | |
| content = msg.text_content[:500] | |
| if msg.tool_uses: | |
| tools = ", ".join(t.name for t in msg.tool_uses) | |
| content += f"\n[dim]Tools: {tools}[/dim]" | |
| console.print(Panel(content, title="Assistant")) | |
| return | |
| console.print(f"[red]Session not found: {session_id}[/red]") | |
| @main.command() | |
| @click.pass_context | |
| def projects(ctx: click.Context) -> None: | |
| """List all projects.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| console = Console() | |
| table = Table(title="Projects") | |
| table.add_column("Path", style="cyan") | |
| table.add_column("Sessions", justify="right") | |
| table.add_column("Latest", style="green") | |
| projects = scanner.scan_all() | |
| for project in projects: | |
| latest = project.latest_session | |
| latest_date = latest.updated_at.strftime("%Y-%m-%d") if latest else "-" | |
| table.add_row(project.display_path, str(project.session_count), latest_date) | |
| console.print(table) | |
| @main.command() | |
| @click.argument("query") | |
| @click.option("--mode", "-m", default="text", help="Search mode: text, title, content") | |
| @click.option("--limit", "-l", default=20, help="Maximum results") | |
| @click.pass_context | |
| def search(ctx: click.Context, query: str, mode: str, limit: int) -> None: | |
| """Search sessions.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.index.search import SearchEngine | |
| config = ctx.obj["config"] | |
| scanner = ClaudeScanner(config.claude_dir) | |
| engine = SearchEngine(scanner) | |
| console = Console() | |
| results = engine.search(query, mode=mode, limit=limit) | |
| if not results: | |
| console.print(f"[yellow]No results for '{query}'[/yellow]") | |
| return | |
| table = Table(title=f"Search: {query}") | |
| table.add_column("Session ID", style="dim") | |
| table.add_column("Score", justify="right", style="cyan") | |
| table.add_column("Session", style="white") | |
| table.add_column("Snippet", style="dim") | |
| for result in results: | |
| session_id = result.session.id[:12] + "..." | |
| title = result.session.title or result.session.id[:8] | |
| snippet = result.snippet[:60] if result.snippet else "" | |
| table.add_row(session_id, f"{result.score:.2f}", title[:40], snippet) | |
| console.print(table) | |
| @main.command() | |
| @click.pass_context | |
| def tui(ctx: click.Context) -> None: | |
| """Launch the interactive TUI.""" | |
| from one_claude.tui.app import OneClaude | |
| app = OneClaude(ctx.obj["config"]) | |
| app.run() | |
| @main.group() | |
| def gist() -> None: | |
| """Gist export/import commands.""" | |
| pass | |
| @gist.command(name="import") | |
| @click.argument("gist_url_or_id") | |
| @click.option("--teleport", "-t", is_flag=True, help="Teleport into the session after import") | |
| @click.option( | |
| "--mode", | |
| "-m", | |
| type=click.Choice(["local", "docker", "microvm"]), | |
| default="docker", | |
| help="Teleport mode: local, docker, or microvm (default: docker)", | |
| ) | |
| @click.option( | |
| "--path", | |
| "-p", | |
| type=click.Path(), | |
| default=None, | |
| help="Path to clone/import project to (default: prompt or cwd)", | |
| ) | |
| @click.pass_context | |
| def gist_import(ctx: click.Context, gist_url_or_id: str, teleport: bool, mode: str, path: str | None) -> None: | |
| """Import a session from a GitHub gist.""" | |
| import asyncio | |
| import os | |
| import subprocess | |
| from pathlib import Path | |
| from rich.console import Console | |
| from one_claude.gist.importer import ExportInfo, SessionImporter | |
| config = ctx.obj["config"] | |
| console = Console() | |
| def clone_repo(git_info: dict, dest_path: str) -> bool: | |
| """Clone git repo and checkout specific commit.""" | |
| remote = git_info.get("remote") | |
| commit = git_info.get("commit") | |
| if not remote: | |
| console.print("[red]No git remote in export[/red]") | |
| return False | |
| console.print(f"[cyan]Cloning {remote}...[/cyan]") | |
| result = subprocess.run( | |
| ["git", "clone", remote, dest_path], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| console.print(f"[red]Clone failed: {result.stderr}[/red]") | |
| return False | |
| if commit: | |
| console.print(f"[cyan]Checking out {commit[:8]}...[/cyan]") | |
| result = subprocess.run( | |
| ["git", "checkout", commit], | |
| cwd=dest_path, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| console.print(f"[yellow]Warning: Could not checkout {commit[:8]}[/yellow]") | |
| return True | |
| async def do_import(): | |
| importer = SessionImporter(config.claude_dir) | |
| # First fetch export info to check if we need to clone | |
| console.print("[dim]Fetching export info...[/dim]") | |
| info = await importer.fetch_export_info(gist_url_or_id) | |
| if isinstance(info, str): | |
| console.print(f"[red]Failed: {info}[/red]") | |
| return | |
| final_path = None | |
| restore_files = False | |
| # If custom path provided, always use it (clone/restore there) | |
| if path: | |
| final_path = os.path.abspath(path) | |
| if os.path.exists(final_path): | |
| console.print(f"[yellow]Path already exists:[/yellow] {final_path}") | |
| if not click.confirm("Use existing directory?"): | |
| return | |
| elif info.git_info and info.git_info.get("remote"): | |
| if not clone_repo(info.git_info, final_path): | |
| return | |
| restore_files = True | |
| else: | |
| restore_files = True # Create dir and restore from checkpoints | |
| # Check if project exists (only if no custom path provided) | |
| elif not info.project_exists: | |
| console.print(f"[yellow]Project directory not found:[/yellow] {info.project_path}") | |
| if info.git_info and info.git_info.get("remote"): | |
| # Prompt for clone path | |
| cwd = os.getcwd() | |
| repo_name = info.git_info["remote"].rstrip("/").split("/")[-1].replace(".git", "") | |
| default_path = os.path.join(cwd, repo_name) | |
| clone_path = click.prompt( | |
| "Clone to", | |
| default=default_path, | |
| type=click.Path(), | |
| ) | |
| clone_path = os.path.abspath(clone_path) | |
| if os.path.exists(clone_path): | |
| console.print(f"[yellow]Path already exists:[/yellow] {clone_path}") | |
| if not click.confirm("Use existing directory?"): | |
| return | |
| else: | |
| if not clone_repo(info.git_info, clone_path): | |
| return | |
| final_path = clone_path | |
| restore_files = True | |
| else: | |
| # No git info - prompt and restore files from checkpoints | |
| cwd = os.getcwd() | |
| dir_name = Path(info.project_path).name | |
| default_path = os.path.join(cwd, dir_name) | |
| final_path = click.prompt( | |
| "Create project at", | |
| default=default_path, | |
| type=click.Path(), | |
| ) | |
| final_path = os.path.abspath(final_path) | |
| restore_files = True | |
| # Do the import | |
| result = await importer.import_session(info, final_path, restore_files=restore_files) | |
| if result.success: | |
| if result.already_imported: | |
| console.print(f"[yellow]Already imported[/yellow]") | |
| console.print(f" Session: {result.session_id}") | |
| console.print(f" Project: {result.project_path}") | |
| else: | |
| console.print(f"[green]Imported successfully![/green]") | |
| console.print(f" Session: {result.session_id}") | |
| console.print(f" Project: {result.project_path}") | |
| console.print(f" Messages: {result.message_count}") | |
| console.print(f" Checkpoints: {result.checkpoint_count}") | |
| if result.files_restored: | |
| console.print(f" Files restored: {result.files_restored}") | |
| if teleport: | |
| console.print(f"\n[cyan]Teleporting into session (mode: {mode})...[/cyan]") | |
| await do_teleport(result.session_id, result.project_path, mode, console) | |
| else: | |
| console.print(f"[red]Import failed: {result.error}[/red]") | |
| async def do_teleport(session_id: str, project_path: str, mode: str, console: Console): | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.teleport.restore import FileRestorer | |
| scanner = ClaudeScanner(config.claude_dir) | |
| restorer = FileRestorer(scanner) | |
| # Find the imported session | |
| session = scanner.get_session_by_id(session_id) | |
| if not session: | |
| console.print(f"[red]Could not find imported session: {session_id}[/red]") | |
| return | |
| # Restore to sandbox (latest message) | |
| teleport_session = await restorer.restore_to_sandbox( | |
| session, | |
| message_uuid=None, # Latest | |
| mode=mode, | |
| ) | |
| sandbox = teleport_session.sandbox | |
| # Get shell command | |
| shell_cmd = sandbox.get_shell_command(term=os.environ.get("TERM")) | |
| # Run the teleport command | |
| try: | |
| subprocess.run(shell_cmd, cwd=sandbox.working_dir) | |
| finally: | |
| await sandbox.stop() | |
| asyncio.run(do_import()) | |
| @gist.command(name="export") | |
| @click.argument("session_id") | |
| @click.pass_context | |
| def gist_export(ctx: click.Context, session_id: str) -> None: | |
| """Export a session to a GitHub gist.""" | |
| import asyncio | |
| from rich.console import Console | |
| from one_claude.core.scanner import ClaudeScanner | |
| from one_claude.gist.exporter import SessionExporter | |
| config = ctx.obj["config"] | |
| console = Console() | |
| scanner = ClaudeScanner(config.claude_dir) | |
| # Find conversation path by session ID | |
| tree_cache = {} | |
| paths = scanner.scan_conversation_paths(tree_cache=tree_cache) | |
| target_path = None | |
| for path in paths: | |
| if path.id == session_id or path.id.startswith(session_id): | |
| target_path = path | |
| break | |
| for jsonl_file in path.jsonl_files: | |
| if jsonl_file.stem == session_id or jsonl_file.stem.startswith(session_id): | |
| target_path = path | |
| break | |
| if not target_path: | |
| console.print(f"[red]Session not found: {session_id}[/red]") | |
| return | |
| async def do_export(): | |
| exporter = SessionExporter(scanner) | |
| result = await exporter.export_full_session(target_path) | |
| if result.success: | |
| console.print(f"[green]Exported successfully![/green]") | |
| console.print(f" URL: {result.gist_url}") | |
| console.print(f" Messages: {result.message_count}") | |
| console.print(f" Checkpoints: {result.checkpoint_count}") | |
| else: | |
| console.print(f"[red]Export failed: {result.error}[/red]") | |
| asyncio.run(do_export()) | |
| @gist.command(name="list") | |
| def gist_list() -> None: | |
| """List exported gists.""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from one_claude.gist.store import load_exports | |
| console = Console() | |
| exports = load_exports() | |
| if not exports: | |
| console.print("[yellow]No exported gists yet[/yellow]") | |
| return | |
| table = Table(title="Exported Gists") | |
| table.add_column("Title", style="white") | |
| table.add_column("Messages", justify="right") | |
| table.add_column("Date", style="green") | |
| table.add_column("URL", style="cyan") | |
| for export in exports: | |
| table.add_row( | |
| export.title[:30], | |
| str(export.message_count), | |
| export.exported_at[:10], | |
| export.gist_url, | |
| ) | |
| console.print(table) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 2: "Code review gold varnishing away" | |
| # Shows ~/.claude directory with valuable context being lost | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: Disk usage | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| $ du -sh ~/.claude/ | |
| 2.3G /home/dev/.claude/ | |
| $ du -sh ~/.claude/projects/*/ | sort -hr | head -10 | |
| 412M /home/dev/.claude/projects/api-gateway/ | |
| 389M /home/dev/.claude/projects/frontend-v2/ | |
| 256M /home/dev/.claude/projects/ml-inference/ | |
| 198M /home/dev/.claude/projects/billing-service/ | |
| 167M /home/dev/.claude/projects/one_claude/ | |
| 145M /home/dev/.claude/projects/data-pipeline/ | |
| 134M /home/dev/.claude/projects/user-service/ | |
| 98M /home/dev/.claude/projects/mobile-app/ | |
| 87M /home/dev/.claude/projects/search-indexer/ | |
| 76M /home/dev/.claude/projects/internal-tools/ | |
| EOF | |
| # Frame 2: What's in there - rich context | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| $ ls ~/.claude/projects/api-gateway/ | |
| sessions/ # 47 conversations | |
| file_history/ # checkpoint diffs | |
| settings.json | |
| $ # Each session contains: | |
| $ # - Full conversation transcript | |
| $ # - Tool calls and results | |
| $ # - File modifications with diffs | |
| $ # - The "why" behind every PR | |
| $ # - Debugging context that took hours | |
| EOF | |
| # Frame 3: The loss | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03.png <<'EOF' | |
| $ # Meanwhile, in 30 days... | |
| $ claude --clear-old-sessions | |
| Cleared 156 sessions older than 30 days | |
| Freed 1.8GB of disk space | |
| $ # All that context. | |
| $ # All those code review insights. | |
| $ # Gone. | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 02_varnishing_context.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 02_varnishing_context.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 2: "Code review gold varnishing away" | |
| # Shows ~/.claude directory with valuable context being lost | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| # Frame 1: Disk usage | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01.png <<'EOF' | |
| $ du -sh ~/.claude/ | |
| 2.3G /home/dev/.claude/ | |
| $ du -sh ~/.claude/projects/*/ | sort -hr | head -10 | |
| 412M /home/dev/.claude/projects/api-gateway/ | |
| 389M /home/dev/.claude/projects/frontend-v2/ | |
| 256M /home/dev/.claude/projects/ml-inference/ | |
| 198M /home/dev/.claude/projects/billing-service/ | |
| 167M /home/dev/.claude/projects/one_claude/ | |
| 145M /home/dev/.claude/projects/data-pipeline/ | |
| 134M /home/dev/.claude/projects/user-service/ | |
| 98M /home/dev/.claude/projects/mobile-app/ | |
| 87M /home/dev/.claude/projects/search-indexer/ | |
| 76M /home/dev/.claude/projects/internal-tools/ | |
| EOF | |
| # Frame 2: What's in there - rich context | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02.png <<'EOF' | |
| $ ls ~/.claude/projects/api-gateway/ | |
| sessions/ # 47 conversations | |
| file_history/ # checkpoint diffs | |
| settings.json | |
| $ # Each session contains: | |
| $ # - Full conversation transcript | |
| $ # - Tool calls and results | |
| $ # - File modifications with diffs | |
| $ # - The "why" behind every PR | |
| $ # - Debugging context that took hours | |
| EOF | |
| # Frame 3: The loss | |
| silicon --from-clipboard false --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03.png <<'EOF' | |
| $ # Meanwhile, in 30 days... | |
| $ claude --clear-old-sessions | |
| Cleared 156 sessions older than 30 days | |
| Freed 1.8GB of disk space | |
| $ # All that context. | |
| $ # All those code review insights. | |
| $ # Gone. | |
| EOF | |
| # Duplicate frames | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Generate palette | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -vf "scale=1200:-1:flags=lanczos,palettegen=max_colors=256:stats_mode=diff" \ | |
| palette.png | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_*.png' \ | |
| -i palette.png \ | |
| -lavfi "scale=1200:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=floyd_steinberg" \ | |
| 02_varnishing_context.gif | |
| # Cleanup | |
| rm -f frame_*.png palette.png | |
| echo "Created 02_varnishing_context.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 2: "Code review gold varnishing away" | |
| # Shows ~/.claude directory with valuable context being lost | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#282a36" | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: Disk usage | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ du -sh ~/.claude/ | |
| 2.3G /home/dev/.claude/ | |
| $ du -sh ~/.claude/projects/*/ | sort -hr | head -10 | |
| 412M ~/.claude/projects/api-gateway/ | |
| 389M ~/.claude/projects/frontend-v2/ | |
| 256M ~/.claude/projects/ml-inference/ | |
| 198M ~/.claude/projects/billing-service/ | |
| 167M ~/.claude/projects/one_claude/ | |
| 145M ~/.claude/projects/data-pipeline/ | |
| 134M ~/.claude/projects/user-service/ | |
| 98M ~/.claude/projects/mobile-app/ | |
| 87M ~/.claude/projects/search-indexer/ | |
| 76M ~/.claude/projects/internal-tools/ | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: What's in there - rich context | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/api-gateway/ | |
| sessions/ # 47 conversations | |
| file_history/ # checkpoint diffs | |
| settings.json | |
| # Each session contains: | |
| # - Full conversation transcript | |
| # - Tool calls and results | |
| # - File modifications with diffs | |
| # - The "why" behind every PR | |
| # - Debugging context that took hours | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The loss | |
| cat > /tmp/frame3.txt <<'EOF' | |
| # Meanwhile, in 30 days... | |
| $ claude --clear-old-sessions | |
| Cleared 156 sessions older than 30 days | |
| Freed 1.8GB of disk space | |
| # All that context. | |
| # All those code review insights. | |
| # Gone. | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 02_varnishing_context.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 02_varnishing_context.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 2: "Code review gold varnishing away" | |
| # Shows ~/.claude directory with valuable context being lost | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#0d1117" | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: Disk usage | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ du -sh ~/.claude/ | |
| 2.3G /home/dev/.claude/ | |
| $ du -sh ~/.claude/projects/*/ | sort -hr | head -10 | |
| 412M ~/.claude/projects/api-gateway/ | |
| 389M ~/.claude/projects/frontend-v2/ | |
| 256M ~/.claude/projects/ml-inference/ | |
| 198M ~/.claude/projects/billing-service/ | |
| 167M ~/.claude/projects/one_claude/ | |
| 145M ~/.claude/projects/data-pipeline/ | |
| 134M ~/.claude/projects/user-service/ | |
| 98M ~/.claude/projects/mobile-app/ | |
| 87M ~/.claude/projects/search-indexer/ | |
| 76M ~/.claude/projects/internal-tools/ | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: What's in there - rich context | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/api-gateway/ | |
| sessions/ # 47 conversations | |
| file_history/ # checkpoint diffs | |
| settings.json | |
| # Each session contains: | |
| # - Full conversation transcript | |
| # - Tool calls and results | |
| # - File modifications with diffs | |
| # - The "why" behind every PR | |
| # - Debugging context that took hours | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The loss | |
| cat > /tmp/frame3.txt <<'EOF' | |
| # Meanwhile, in 30 days... | |
| $ claude --clear-old-sessions | |
| Cleared 156 sessions older than 30 days | |
| Freed 1.8GB of disk space | |
| # All that context. | |
| # All those code review insights. | |
| # Gone. | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 02_varnishing_context.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 02_varnishing_context.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Tweet 2: "Code review gold varnishing away" | |
| # Shows ~/.claude directory with valuable context being lost | |
| set -e | |
| cd "$(dirname "$0")" | |
| FONT="Hack" | |
| THEME="Dracula" | |
| BG="#0d1117" | |
| TARGET_W=1200 | |
| TARGET_H=700 | |
| # Frame 1: Disk usage | |
| cat > /tmp/frame1.txt <<'EOF' | |
| $ du -sh ~/.claude/ | |
| 2.3G /home/dev/.claude/ | |
| $ du -sh ~/.claude/projects/*/ | sort -hr | head -10 | |
| 412M ~/.claude/projects/api-gateway/ | |
| 389M ~/.claude/projects/frontend-v2/ | |
| 256M ~/.claude/projects/ml-inference/ | |
| 198M ~/.claude/projects/billing-service/ | |
| 167M ~/.claude/projects/one_claude/ | |
| 145M ~/.claude/projects/data-pipeline/ | |
| 134M ~/.claude/projects/user-service/ | |
| 98M ~/.claude/projects/mobile-app/ | |
| 87M ~/.claude/projects/search-indexer/ | |
| 76M ~/.claude/projects/internal-tools/ | |
| EOF | |
| silicon /tmp/frame1.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_01_raw.png | |
| # Frame 2: What's in there - rich context | |
| cat > /tmp/frame2.txt <<'EOF' | |
| $ ls ~/.claude/projects/api-gateway/ | |
| sessions/ # 47 conversations | |
| file_history/ # checkpoint diffs | |
| settings.json | |
| # Each session contains: | |
| # - Full conversation transcript | |
| # - Tool calls and results | |
| # - File modifications with diffs | |
| # - The "why" behind every PR | |
| # - Debugging context that took hours | |
| EOF | |
| silicon /tmp/frame2.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_02_raw.png | |
| # Frame 3: The loss | |
| cat > /tmp/frame3.txt <<'EOF' | |
| # Meanwhile, disk is getting full... | |
| $ rm -rf ~/.claude/projects/old-client/ | |
| $ rm -rf ~/.claude/projects/legacy-api/ | |
| $ rm -rf ~/.claude/projects/2024-* | |
| # All that context. | |
| # All those code review insights. | |
| # Gone. | |
| EOF | |
| silicon /tmp/frame3.txt --language bash --font "$FONT" --theme "$THEME" \ | |
| --background "$BG" --pad-horiz 40 --pad-vert 40 \ | |
| --output frame_03_raw.png | |
| # Normalize all frames to target size | |
| for i in 1 2 3; do | |
| ffmpeg -y -i "frame_0${i}_raw.png" \ | |
| -vf "scale='min($TARGET_W,iw)':'min($TARGET_H,ih)':force_original_aspect_ratio=decrease,pad=$TARGET_W:$TARGET_H:(ow-iw)/2:(oh-ih)/2:color=$BG" \ | |
| "frame_0${i}.png" 2>/dev/null | |
| done | |
| # Duplicate frames for timing | |
| for i in 1 2 3; do | |
| for j in $(seq 1 15); do | |
| cp "frame_0${i}.png" "frame_0${i}_$(printf '%02d' $j).png" | |
| done | |
| done | |
| # Create GIF | |
| ffmpeg -y -framerate 10 -pattern_type glob -i 'frame_0?_??.png' \ | |
| -vf "split[s0][s1];[s0]palettegen=max_colors=256[p];[s1][p]paletteuse=dither=floyd_steinberg" \ | |
| 02_varnishing_context.gif 2>/dev/null | |
| # Cleanup | |
| rm -f frame_*.png /tmp/frame*.txt | |
| echo "Created 02_varnishing_context.gif" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Import this Claude session: curl -sL <raw_url> | bash | |
| if ! command -v uvx &> /dev/null; then | |
| echo "Installing uv..." | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| export PATH="$HOME/.local/bin:$PATH" | |
| fi | |
| uvx one_claude gist import cddcffc229f259351e808ac45c3ad4d4 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "version": "1.0", | |
| "export_type": "from_message", | |
| "exported_at": "2026-01-13T03:48:03.498267+00:00", | |
| "exporter": "one_claude", | |
| "session": { | |
| "id": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "project_path": "/home/tato/Desktop/one_claude", | |
| "title": "I'm making a tweet thread about this project.", | |
| "created_at": "2026-01-13T02:51:57.939000+00:00", | |
| "updated_at": "2026-01-13T03:47:11.355000+00:00" | |
| }, | |
| "git_info": { | |
| "branch": "main", | |
| "commit": "29264cd15f2948f2933aa743bf3aef820b61c45d", | |
| "remote": "git@github.com:phact/one_claude.git", | |
| "all_remotes": { | |
| "origin": "git@github.com:phact/one_claude.git" | |
| } | |
| }, | |
| "messages": [ | |
| { | |
| "parentUuid": "c39210f1-f2c1-415a-8e87-3deb4c7b2ba0", | |
| "isSidechain": false, | |
| "userType": "external", | |
| "cwd": "/home/tato/Desktop/one_claude", | |
| "sessionId": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "version": "2.1.6", | |
| "gitBranch": "main", | |
| "slug": "lazy-wobbling-sunbeam", | |
| "message": { | |
| "model": "claude-opus-4-5-20251101", | |
| "id": "msg_015FKh8kpYrh3PMC8kqUvfYY", | |
| "type": "message", | |
| "role": "assistant", | |
| "content": [ | |
| { | |
| "type": "text", | |
| "text": "Now I can see the session ID. This session is `4f118ac2-6a8...`. Let me export it:" | |
| } | |
| ], | |
| "stop_reason": null, | |
| "stop_sequence": null, | |
| "usage": { | |
| "input_tokens": 8, | |
| "cache_creation_input_tokens": 646, | |
| "cache_read_input_tokens": 110069, | |
| "cache_creation": { | |
| "ephemeral_5m_input_tokens": 646, | |
| "ephemeral_1h_input_tokens": 0 | |
| }, | |
| "output_tokens": 2, | |
| "service_tier": "standard" | |
| } | |
| }, | |
| "requestId": "req_011CX4h2xSo4DZbJ7TUZqe9v", | |
| "type": "assistant", | |
| "uuid": "52967145-672d-4767-b2d1-efb318828d28", | |
| "timestamp": "2026-01-13T03:46:35.597Z" | |
| }, | |
| { | |
| "parentUuid": "52967145-672d-4767-b2d1-efb318828d28", | |
| "isSidechain": false, | |
| "userType": "external", | |
| "cwd": "/home/tato/Desktop/one_claude", | |
| "sessionId": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "version": "2.1.6", | |
| "gitBranch": "main", | |
| "slug": "lazy-wobbling-sunbeam", | |
| "message": { | |
| "model": "claude-opus-4-5-20251101", | |
| "id": "msg_015FKh8kpYrh3PMC8kqUvfYY", | |
| "type": "message", | |
| "role": "assistant", | |
| "content": [ | |
| { | |
| "type": "tool_use", | |
| "id": "toolu_01SU6S383NHd24GsDnuoyfra", | |
| "name": "Bash", | |
| "input": { | |
| "command": "uv run one_claude gist export 4f118ac2-6a8 2>&1", | |
| "description": "Export this session to gist", | |
| "timeout": 60000 | |
| } | |
| } | |
| ], | |
| "stop_reason": null, | |
| "stop_sequence": null, | |
| "usage": { | |
| "input_tokens": 8, | |
| "cache_creation_input_tokens": 646, | |
| "cache_read_input_tokens": 110069, | |
| "cache_creation": { | |
| "ephemeral_5m_input_tokens": 646, | |
| "ephemeral_1h_input_tokens": 0 | |
| }, | |
| "output_tokens": 2, | |
| "service_tier": "standard" | |
| } | |
| }, | |
| "requestId": "req_011CX4h2xSo4DZbJ7TUZqe9v", | |
| "type": "assistant", | |
| "uuid": "c256d055-8bce-4d39-a7bb-92fe80aae72d", | |
| "timestamp": "2026-01-13T03:46:36.639Z" | |
| }, | |
| { | |
| "parentUuid": "c256d055-8bce-4d39-a7bb-92fe80aae72d", | |
| "isSidechain": false, | |
| "userType": "external", | |
| "cwd": "/home/tato/Desktop/one_claude", | |
| "sessionId": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "version": "2.1.6", | |
| "gitBranch": "main", | |
| "slug": "lazy-wobbling-sunbeam", | |
| "type": "user", | |
| "message": { | |
| "role": "user", | |
| "content": [ | |
| { | |
| "tool_use_id": "toolu_01SU6S383NHd24GsDnuoyfra", | |
| "type": "tool_result", | |
| "content": "Export failed: GitHub API error: 502", | |
| "is_error": false | |
| } | |
| ] | |
| }, | |
| "uuid": "c9fe1ea8-92af-46af-b7bd-842429a88abe", | |
| "timestamp": "2026-01-13T03:47:04.804Z", | |
| "toolUseResult": { | |
| "stdout": "Export failed: GitHub API error: 502", | |
| "stderr": "", | |
| "interrupted": false, | |
| "isImage": false | |
| }, | |
| "sourceToolAssistantUUID": "c256d055-8bce-4d39-a7bb-92fe80aae72d" | |
| }, | |
| { | |
| "parentUuid": "c9fe1ea8-92af-46af-b7bd-842429a88abe", | |
| "isSidechain": false, | |
| "userType": "external", | |
| "cwd": "/home/tato/Desktop/one_claude", | |
| "sessionId": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "version": "2.1.6", | |
| "gitBranch": "main", | |
| "slug": "lazy-wobbling-sunbeam", | |
| "message": { | |
| "model": "claude-opus-4-5-20251101", | |
| "id": "msg_01StXGYoKaxuVDtqaiGjEatm", | |
| "type": "message", | |
| "role": "assistant", | |
| "content": [ | |
| { | |
| "type": "thinking", | |
| "thinking": "Got a 502 error from GitHub API. Let me try again.", | |
| "signature": "EtoBCkYICxgCKkD+71uL2Ogr78exuroTLcXP/4hSnR7db26MVK4xBU/msidUlB/Fw1mxzp8AJkHU8Qc/v+b6hmkXsLecuMAVPZQyEgw46/gmyTMiphFGTR4aDLWT95BQouepdMWkoCIwmpzmTNqpPldE5w2RVlxNEtZ55mBQlr9VdvjD72TDSooYvAlQXN2PmH1I5Rm9m3QXKkIuN0ACnpmh0Fjj7gLf7QVBG4xnL1NdUvfWHwJp26H4pIpNSvo47r9Lfp9CXczGcXtR2WdPGX5TYtK0DoRt5rFyRVAYAQ==" | |
| } | |
| ], | |
| "stop_reason": null, | |
| "stop_sequence": null, | |
| "usage": { | |
| "input_tokens": 8, | |
| "cache_creation_input_tokens": 219, | |
| "cache_read_input_tokens": 110715, | |
| "cache_creation": { | |
| "ephemeral_5m_input_tokens": 219, | |
| "ephemeral_1h_input_tokens": 0 | |
| }, | |
| "output_tokens": 1, | |
| "service_tier": "standard" | |
| } | |
| }, | |
| "requestId": "req_011CX4h5bmvMXEWK3FadDbxe", | |
| "type": "assistant", | |
| "uuid": "015a055b-4c8b-4cff-b2c0-b454b8215e4f", | |
| "timestamp": "2026-01-13T03:47:10.585Z" | |
| }, | |
| { | |
| "parentUuid": "015a055b-4c8b-4cff-b2c0-b454b8215e4f", | |
| "isSidechain": false, | |
| "userType": "external", | |
| "cwd": "/home/tato/Desktop/one_claude", | |
| "sessionId": "4f118ac2-6a8b-47c7-a743-5eaa85a4f81a", | |
| "version": "2.1.6", | |
| "gitBranch": "main", | |
| "slug": "lazy-wobbling-sunbeam", | |
| "message": { | |
| "model": "claude-opus-4-5-20251101", | |
| "id": "msg_01StXGYoKaxuVDtqaiGjEatm", | |
| "type": "message", | |
| "role": "assistant", | |
| "content": [ | |
| { | |
| "type": "tool_use", | |
| "id": "toolu_01S96nhuUzSRksgKBE5J3MSZ", | |
| "name": "Bash", | |
| "input": { | |
| "command": "uv run one_claude gist export 4f118ac2-6a8 2>&1", | |
| "description": "Retry gist export", | |
| "timeout": 60000 | |
| } | |
| } | |
| ], | |
| "stop_reason": null, | |
| "stop_sequence": null, | |
| "usage": { | |
| "input_tokens": 8, | |
| "cache_creation_input_tokens": 219, | |
| "cache_read_input_tokens": 110715, | |
| "cache_creation": { | |
| "ephemeral_5m_input_tokens": 219, | |
| "ephemeral_1h_input_tokens": 0 | |
| }, | |
| "output_tokens": 1, | |
| "service_tier": "standard" | |
| } | |
| }, | |
| "requestId": "req_011CX4h5bmvMXEWK3FadDbxe", | |
| "type": "assistant", | |
| "uuid": "5afa3e05-deb4-4342-b988-adff5e8e2f05", | |
| "timestamp": "2026-01-13T03:47:11.355Z" | |
| } | |
| ], | |
| "checkpoint_manifest": { | |
| "cb6f8e282c9f2e0e": { | |
| "original_path": "/home/tato/Desktop/one_claude/one_claude/cli.py", | |
| "versions": [ | |
| 1, | |
| 2 | |
| ] | |
| }, | |
| "7925468215bf1f2b": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/06_hero.tape", | |
| "versions": [ | |
| 1, | |
| 2 | |
| ] | |
| }, | |
| "a68f4545951749a8": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/07_meta_closing.sh", | |
| "versions": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ] | |
| }, | |
| "9e3d511d29baddc9": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/05_teleport.tape", | |
| "versions": [ | |
| 1, | |
| 2, | |
| 3 | |
| ] | |
| }, | |
| "4410c78633969e71": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/generate_all.sh", | |
| "versions": [ | |
| 1, | |
| 2 | |
| ] | |
| }, | |
| "f669fd8510b911b1": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/02_varnishing_context.sh", | |
| "versions": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4, | |
| 5 | |
| ] | |
| }, | |
| "0aee1abca745e057": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/03_lost_rewinds.sh", | |
| "versions": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ] | |
| }, | |
| "3d6baffb8893cc78": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/01_too_many_claudes.sh", | |
| "versions": [ | |
| 2, | |
| 3, | |
| 4, | |
| 5 | |
| ] | |
| }, | |
| "5f54840062e592f8": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/00_meta_teleport.tape", | |
| "versions": [ | |
| 1, | |
| 2 | |
| ] | |
| }, | |
| "9397a6bef82371af": { | |
| "original_path": "/home/tato/Desktop/one_claude/images/04_tui_tree.tape", | |
| "versions": [ | |
| 1, | |
| 2 | |
| ] | |
| } | |
| }, | |
| "from_message_uuid": "52967145-672d-4767-b2d1-efb318828d28" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment