Full conversation backup — sync on every push
Register on the home page to get your API key, then paste this into any terminal. It installs the sync script, configures Claude Code to auto-backup after every session, and immediately syncs all your existing sessions.
bash <<'SETUP'
# ── Claude Session Registry: one-paste setup ──
# Replace YOUR_API_KEY below with your actual key from repo.ittlh.dev
CSR_API_KEY="${CSR_API_KEY:-YOUR_API_KEY}"
CSR_URL="${CSR_URL:-https://repo.ittlh.dev}"
if [ "$CSR_API_KEY" = "YOUR_API_KEY" ]; then
echo "ERROR: Set CSR_API_KEY first."
echo " export CSR_API_KEY=your-key-here"
echo "Then re-run this script."
exit 1
fi
# 1) Persist env vars
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
[ -f "$rc" ] || continue
grep -q CSR_API_KEY "$rc" 2>/dev/null && continue
printf '\n# Claude Session Registry\nexport CSR_API_KEY="%s"\nexport CSR_URL="%s"\n' "$CSR_API_KEY" "$CSR_URL" >> "$rc"
echo "✓ Added env vars to $rc"
done
# 2) Install sync script
mkdir -p "$HOME/.local/bin"
cat > "$HOME/.local/bin/claude-session-sync" <<'SYNC'
#!/usr/bin/env bash
set -euo pipefail
API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"
[ -d "$CLAUDE_DIR" ] || { echo "No Claude projects at $CLAUDE_DIR"; exit 1; }
synced=0; failed=0
for project_dir in "$CLAUDE_DIR"/*/; do
[ -d "$project_dir" ] || continue
project_name=$(basename "$project_dir")
working_dir=$(echo "$project_name" | sed 's|--|/|g; s|^/||')
for session_file in "$project_dir"*.jsonl; do
[ -f "$session_file" ] || continue
session_id=$(basename "$session_file" .jsonl)
echo "$session_id" | grep -qE '^[0-9a-f]{8}-' || continue
http_code=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
"${BASE_URL}/api/sessions/conversation/${session_id}" \
-H "X-API-Key: $API_KEY" \
-H "X-Working-Directory: $working_dir" \
-H "X-Project-Name: $project_name" \
-H "Content-Type: application/x-ndjson" \
--data-binary "@${session_file}")
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
synced=$((synced + 1))
else
failed=$((failed + 1))
echo " FAILED: ${project_name}/${session_id} (HTTP $http_code)"
fi
done
done
echo "✓ Sync complete: $synced ok, $failed failed."
SYNC
chmod +x "$HOME/.local/bin/claude-session-sync"
echo "✓ Installed sync script to ~/.local/bin/claude-session-sync"
# 3) Add ~/.local/bin to PATH if not already there
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
[ -f "$rc" ] || continue
grep -q '\.local/bin' "$rc" 2>/dev/null && continue
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$rc"
done
export PATH="$HOME/.local/bin:$PATH"
echo "✓ Added ~/.local/bin to PATH" ;;
esac
# 4) Configure Claude Code hook (auto-sync after every session)
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
mkdir -p "$HOME/.claude"
HOOK='{"hooks":[{"type":"command","command":"claude-session-sync 2>/dev/null &"}]}'
if [ -f "$CLAUDE_SETTINGS" ]; then
if ! grep -q 'claude-session-sync' "$CLAUDE_SETTINGS" 2>/dev/null; then
jq --argjson hook "$HOOK" '.hooks.Stop = (.hooks.Stop // []) + [$hook]' "$CLAUDE_SETTINGS" > "${CLAUDE_SETTINGS}.tmp"
mv "${CLAUDE_SETTINGS}.tmp" "$CLAUDE_SETTINGS"
echo "✓ Added sync hook to Claude Code settings"
else
echo "✓ Sync hook already configured"
fi
else
printf '{"hooks":{"Stop":[%s]}}\n' "$HOOK" > "$CLAUDE_SETTINGS"
echo "✓ Created Claude Code settings with sync hook"
fi
# 5) Run initial sync of ALL existing sessions
echo ""
echo "Running initial sync of all existing sessions..."
claude-session-sync
echo ""
echo "═══════════════════════════════════════════════"
echo " ✓ Setup complete!"
echo " All existing sessions are backed up."
echo " New sessions will auto-sync when they end."
echo " Dashboard: https://repo.ittlh.dev"
echo "═══════════════════════════════════════════════"
SETUP
What this does: Installs the sync script, adds env vars to your shell, configures a Claude Code hook so every session auto-syncs when it ends, and immediately backs up all existing sessions (full conversation JSONL files, not just metadata).
If you prefer to set things up piece by piece, follow the steps below.
Register on the home page to get an API key, then set these env vars:
export CSR_API_KEY="your-api-key-here" export CSR_URL="https://repo.ittlh.dev"
Add to ~/.bashrc / ~/.zshrc / $PROFILE so they persist.
Save as ~/.local/bin/claude-session-sync and chmod +x it. This backs up full conversation history (JSONL files), not just metadata.
#!/usr/bin/env bash
# claude-session-sync — back up Claude Code sessions + conversations
# Uploads session metadata AND full conversation JSONL files to the registry.
set -euo pipefail
API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"
if [ ! -d "$CLAUDE_DIR" ]; then
echo "No Claude projects directory at $CLAUDE_DIR"
exit 1
fi
# Build manifest and collect conversation files
manifest_sessions="[]"
curl_parts=()
for project_dir in "$CLAUDE_DIR"/*/; do
[ -d "$project_dir" ] || continue
project_name=$(basename "$project_dir")
# Decode working directory from project folder name
working_dir=$(echo "$project_name" | sed 's|--|/|g; s|^/||')
for session_file in "$project_dir"*.jsonl; do
[ -f "$session_file" ] || continue
session_id=$(basename "$session_file" .jsonl)
# Skip non-UUID filenames (not session files)
if ! echo "$session_id" | grep -qE '^[0-9a-f]{8}-'; then
continue
fi
manifest_sessions=$(echo "$manifest_sessions" | jq \
--arg sid "$session_id" \
--arg wd "$working_dir" \
--arg pn "$project_name" \
'. + [{"session_id": $sid, "working_directory": $wd, "project_name": $pn, "name": $pn}]')
# Add conversation file to upload
curl_parts+=(-F "conversation_${session_id}=@${session_file}")
done
done
count=$(echo "$manifest_sessions" | jq length)
if [ "$count" -eq 0 ]; then
echo "No sessions found to sync."
exit 0
fi
echo "Syncing $count sessions with conversations..."
manifest=$(jq -n --argjson sessions "$manifest_sessions" '{"sessions": $sessions}')
response=$(curl -s -X POST "${BASE_URL}/api/sessions/sync" \
-H "X-API-Key: $API_KEY" \
-F "manifest=$manifest" \
"${curl_parts[@]}")
echo "$response" | jq .
echo "Sync complete."
Pick one (or combine them) so backups happen automatically.
Add to ~/.claude/settings.json — triggers when Claude finishes a task:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Stop",
"command": "claude-session-sync 2>/dev/null &"
}
]
}
}
Global hook — applies to all repos:
mkdir -p ~/.git-hooks git config --global core.hooksPath ~/.git-hooks cat > ~/.git-hooks/post-commit << 'HOOK' #!/usr/bin/env bash claude-session-sync 2>/dev/null & HOOK chmod +x ~/.git-hooks/post-commit
# Sync every 15 minutes (crontab -l 2>/dev/null; echo "*/15 * * * * ~/.local/bin/claude-session-sync >> /tmp/csr-sync.log 2>&1") | crontab -
# PowerShell (admin) — sync every 15 min via WSL/Git Bash $action = New-ScheduledTaskAction -Execute "bash.exe" ` -Argument "-lc 'source ~/.bashrc; claude-session-sync'" $trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 15) ` -Once -At (Get-Date) Register-ScheduledTask -TaskName "ClaudeSessionSync" ` -Action $action -Trigger $trigger ` -Description "Sync Claude Code sessions to registry"
After a crash or on a new machine — download all conversations and restore:
#!/usr/bin/env bash
# claude-session-restore — pull all sessions + conversations from the registry
set -euo pipefail
API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"
echo "Fetching session list..."
sessions=$(curl -s "${BASE_URL}/api/sessions" -H "X-API-Key: $API_KEY")
count=$(echo "$sessions" | jq length)
echo "Found $count sessions to restore."
echo "$sessions" | jq -c '.[]' | while read -r session; do
sid=$(echo "$session" | jq -r '.session_id')
project=$(echo "$session" | jq -r '.project_name // .working_directory')
wd=$(echo "$session" | jq -r '.working_directory')
conv_size=$(echo "$session" | jq -r '.conversation_size // 0')
# Encode project dir name the way Claude Code does
project_dir=$(echo "$project" | sed 's|/|--|g')
target_dir="${CLAUDE_DIR}/${project_dir}"
mkdir -p "$target_dir"
if [ "$conv_size" -gt 0 ]; then
echo "Downloading conversation for ${sid} (${project_dir})..."
curl -s "${BASE_URL}/api/sessions/conversation/${sid}" \
-H "X-API-Key: $API_KEY" \
-o "${target_dir}/${sid}.jsonl"
fi
echo " → To resume: cd \"${wd}\" && claude --resume ${sid}"
done
echo ""
echo "Restore complete! Sessions are in ${CLAUDE_DIR}/"
echo "Use 'claude --resume SESSION_ID' in the appropriate directory to continue."
Export your Claude sessions as Obsidian-compatible markdown notes with YAML frontmatter. Works with Dataview, search, graph view, and tags.
#!/usr/bin/env bash
# claude-obsidian-sync — export sessions to Obsidian vault as markdown
set -euo pipefail
API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
VAULT="${OBSIDIAN_VAULT:-$HOME/Documents/obsidian}"
TARGET_DIR="${VAULT}/Claude Sessions"
mkdir -p "$TARGET_DIR"
echo "Fetching sessions for Obsidian export..."
sessions=$(curl -s "${BASE_URL}/api/sessions/export/obsidian" \
-H "X-API-Key: $API_KEY")
count=$(echo "$sessions" | jq length)
echo "Exporting $count sessions to ${TARGET_DIR}..."
echo "$sessions" | jq -c '.[]' | while read -r entry; do
filename=$(echo "$entry" | jq -r '.filename' | sed 's/[<>:"|?*]/-/g')
session_id=$(echo "$entry" | jq -r '.session_id')
content=$(echo "$entry" | jq -r '.content')
echo "$content" > "${TARGET_DIR}/${filename}"
echo " ✓ ${filename}"
done
echo ""
echo "Done! Open your Obsidian vault to see the notes."
echo "Each note has frontmatter tags for Dataview queries."
export OBSIDIAN_VAULT="$HOME/Documents/obsidian"
# Add after claude-session-sync in your hook or cron: claude-obsidian-sync 2>/dev/null &
Once synced, use these Dataview queries in any note:
```dataview TABLE working_directory, updated, conversation_lines FROM #claude-session SORT updated DESC ``` ```dataview LIST FROM #claude-session WHERE contains(project, "SFLV2") SORT updated DESC ``` ```dataview TABLE conversation_size as "Size", conversation_lines as "Messages" FROM #claude-session WHERE conversation_size > 0 SORT conversation_size DESC ```
Each session becomes a markdown file with:
#claude-session tag for filtering| Session metadata | Session ID, working directory, project name |
| Full conversation | Complete JSONL transcript — every message, tool call, and result |
| Code context | File reads, edits, and diffs are embedded in the conversation |
| Resumability | Restored sessions can be resumed with claude --resume |
All endpoints require X-API-Key header.
POST /api/auth/register { "username": "..." } → { api_key }
GET /api/auth/me whoami
GET /api/sessions list sessions (?project= filter)
POST /api/sessions register a session
POST /api/sessions/sync bulk sync (JSON or multipart w/ conversations)
PUT /api/sessions/:id update session
DELETE /api/sessions/:id delete session
PUT /api/sessions/conversation/:sessionId upload conversation JSONL (raw body)
GET /api/sessions/conversation/:sessionId download conversation JSONL
GET /api/sessions/export/obsidian all sessions as markdown (JSON array)
GET /api/sessions/export/obsidian?session_id= single session as .md file