@@ -4833,6 +4833,310 @@ handle_handoff_command() {
48334833 esac
48344834}
48354835
4836+ # =============================================================================
4837+ # Time Travel - Automatic snapshots and workspace rewind
4838+ # =============================================================================
4839+
4840+ SNAPSHOT_DIR=" $CONFIG_DIR /snapshots"
4841+ SNAPSHOT_INTERVAL=" ${CRAB_SNAPSHOT_INTERVAL:- 900} " # Default: 15 minutes
4842+
4843+ # Create a snapshot of workspace state
4844+ create_snapshot () {
4845+ local num=" $1 "
4846+ local label=" ${2:- auto} "
4847+ local dir=" $WORKSPACE_BASE /$WORKSPACE_PREFIX -$num "
4848+
4849+ if [ ! -d " $dir " ]; then
4850+ return 1
4851+ fi
4852+
4853+ mkdir -p " $SNAPSHOT_DIR /ws-$num "
4854+
4855+ local timestamp=$( date +%s)
4856+ local snapshot_name=" $timestamp -$label "
4857+ local snapshot_path=" $SNAPSHOT_DIR /ws-$num /$snapshot_name "
4858+
4859+ mkdir -p " $snapshot_path "
4860+
4861+ cd " $dir "
4862+
4863+ # Save git state
4864+ local branch=$( git branch --show-current 2> /dev/null || echo " main" )
4865+ local commit=$( git rev-parse HEAD 2> /dev/null || echo " " )
4866+ local diff=$( git diff HEAD 2> /dev/null || echo " " )
4867+ local staged=$( git diff --cached HEAD 2> /dev/null || echo " " )
4868+ local stash_count=$( git stash list 2> /dev/null | wc -l | tr -d ' ' )
4869+
4870+ echo " $branch " > " $snapshot_path /branch"
4871+ echo " $commit " > " $snapshot_path /commit"
4872+ [ -n " $diff " ] && echo " $diff " > " $snapshot_path /unstaged.patch"
4873+ [ -n " $staged " ] && echo " $staged " > " $snapshot_path /staged.patch"
4874+
4875+ # Save metadata
4876+ cat > " $snapshot_path /metadata.json" << EOF
4877+ {
4878+ "timestamp": $timestamp ,
4879+ "label": "$label ",
4880+ "branch": "$branch ",
4881+ "commit": "$commit ",
4882+ "has_unstaged": $( [ -n " $diff " ] && echo " true" || echo " false" ) ,
4883+ "has_staged": $( [ -n " $staged " ] && echo " true" || echo " false" ) ,
4884+ "stash_count": $stash_count ,
4885+ "created_at": "$( date -Iseconds) "
4886+ }
4887+ EOF
4888+
4889+ # Prune old snapshots (keep last 50)
4890+ local snapshot_count=$( ls -1d " $SNAPSHOT_DIR /ws-$num " /* / 2> /dev/null | wc -l | tr -d ' ' )
4891+ if [ " $snapshot_count " -gt 50 ]; then
4892+ ls -1d " $SNAPSHOT_DIR /ws-$num " /* / 2> /dev/null | head -n -50 | xargs rm -rf 2> /dev/null || true
4893+ fi
4894+
4895+ echo " $snapshot_name "
4896+ }
4897+
4898+ # Manual snapshot with label
4899+ save_snapshot () {
4900+ local num=" $1 "
4901+ local label=" ${2:- manual} "
4902+
4903+ echo -e " ${CYAN} Creating snapshot...${NC} "
4904+ local snapshot_name=$( create_snapshot " $num " " $label " )
4905+
4906+ if [ -n " $snapshot_name " ]; then
4907+ success " Snapshot saved: $label "
4908+ echo -e " ${GRAY} Restore with: crab rewind $num $snapshot_name ${NC} "
4909+ else
4910+ error " Failed to create snapshot"
4911+ fi
4912+ }
4913+
4914+ # List snapshots for a workspace
4915+ list_snapshots () {
4916+ local num=" $1 "
4917+ local limit=" ${2:- 20} "
4918+ local snapshot_base=" $SNAPSHOT_DIR /ws-$num "
4919+
4920+ if [ ! -d " $snapshot_base " ]; then
4921+ echo -e " ${YELLOW} No snapshots for workspace $num ${NC} "
4922+ return
4923+ fi
4924+
4925+ echo -e " ${CYAN} ╭────────────────────────────────────────────────────╮${NC} "
4926+ echo -e " ${CYAN} │${NC} ${BOLD} 🕐 Time Travel - Workspace $num ${NC} ${CYAN} │${NC} "
4927+ echo -e " ${CYAN} ╰────────────────────────────────────────────────────╯${NC} "
4928+ echo " "
4929+
4930+ local count=0
4931+ for snapshot_dir in $( ls -1dr " $snapshot_base " /* / 2> /dev/null) ; do
4932+ [ -d " $snapshot_dir " ] || continue
4933+ count=$(( count + 1 ))
4934+ [ $count -gt $limit ] && break
4935+
4936+ local snapshot_name=$( basename " $snapshot_dir " )
4937+ local timestamp=$( echo " $snapshot_name " | cut -d' -' -f1)
4938+ local label=$( echo " $snapshot_name " | cut -d' -' -f2-)
4939+
4940+ # Format time relative to now
4941+ local now=$( date +%s)
4942+ local diff=$(( now - timestamp))
4943+ local time_ago=" "
4944+
4945+ if [ $diff -lt 60 ]; then
4946+ time_ago=" ${diff} s ago"
4947+ elif [ $diff -lt 3600 ]; then
4948+ time_ago=" $(( diff / 60 )) m ago"
4949+ elif [ $diff -lt 86400 ]; then
4950+ time_ago=" $(( diff / 3600 )) h ago"
4951+ else
4952+ time_ago=" $(( diff / 86400 )) d ago"
4953+ fi
4954+
4955+ # Read metadata
4956+ local branch=" "
4957+ local has_changes=" clean"
4958+ if [ -f " $snapshot_dir /metadata.json" ] && command_exists jq; then
4959+ branch=$( jq -r " .branch // \"\" " " $snapshot_dir /metadata.json" 2> /dev/null)
4960+ local has_unstaged=$( jq -r " .has_unstaged // false" " $snapshot_dir /metadata.json" 2> /dev/null)
4961+ local has_staged=$( jq -r " .has_staged // false" " $snapshot_dir /metadata.json" 2> /dev/null)
4962+ [ " $has_unstaged " = " true" ] || [ " $has_staged " = " true" ] && has_changes=" changes"
4963+ else
4964+ [ -f " $snapshot_dir /branch" ] && branch=$( cat " $snapshot_dir /branch" )
4965+ [ -f " $snapshot_dir /unstaged.patch" ] || [ -f " $snapshot_dir /staged.patch" ] && has_changes=" changes"
4966+ fi
4967+
4968+ local status_icon=" ✓"
4969+ local status_color=" ${GREEN} "
4970+ if [ " $has_changes " = " changes" ]; then
4971+ status_icon=" ●"
4972+ status_color=" ${YELLOW} "
4973+ fi
4974+
4975+ printf " ${BOLD} %2d.${NC} %-12s ${GRAY} %-10s${NC} ${status_color} %s${NC} %s\n" \
4976+ " $count " " $time_ago " " $label " " $status_icon " " $branch "
4977+ done
4978+
4979+ echo " "
4980+ echo -e " ${GRAY} Usage: crab rewind $num <N> (restore snapshot #N)${NC} "
4981+ echo -e " ${GRAY} crab rewind $num 1h (restore from ~1 hour ago)${NC} "
4982+ echo " "
4983+ }
4984+
4985+ # Rewind to a specific snapshot
4986+ rewind_to_snapshot () {
4987+ local num=" $1 "
4988+ local target=" $2 "
4989+ local dir=" $WORKSPACE_BASE /$WORKSPACE_PREFIX -$num "
4990+ local snapshot_base=" $SNAPSHOT_DIR /ws-$num "
4991+
4992+ if [ ! -d " $dir " ]; then
4993+ error " Workspace $num does not exist"
4994+ exit 1
4995+ fi
4996+
4997+ if [ ! -d " $snapshot_base " ]; then
4998+ error " No snapshots for workspace $num "
4999+ exit 1
5000+ fi
5001+
5002+ local snapshot_dir=" "
5003+
5004+ # Handle different target formats
5005+ if [[ " $target " =~ ^[0-9]+$ ]]; then
5006+ # Target is a snapshot number (1, 2, 3...)
5007+ snapshot_dir=$( ls -1dr " $snapshot_base " /* / 2> /dev/null | sed -n " ${target} p" )
5008+ elif [[ " $target " =~ ^[0-9]+[mhd]$ ]]; then
5009+ # Target is a time offset (30m, 2h, 1d)
5010+ local amount=${target% ?}
5011+ local unit=${target: -1}
5012+ local seconds=0
5013+
5014+ case " $unit " in
5015+ " m" ) seconds=$(( amount * 60 )) ;;
5016+ " h" ) seconds=$(( amount * 3600 )) ;;
5017+ " d" ) seconds=$(( amount * 86400 )) ;;
5018+ esac
5019+
5020+ local target_time=$(( $(date +% s) - seconds))
5021+
5022+ # Find snapshot closest to target time
5023+ for sdir in $( ls -1dr " $snapshot_base " /* / 2> /dev/null) ; do
5024+ local snap_time=$( basename " $sdir " | cut -d' -' -f1)
5025+ if [ " $snap_time " -le " $target_time " ]; then
5026+ snapshot_dir=" $sdir "
5027+ break
5028+ fi
5029+ done
5030+ else
5031+ # Target is a snapshot name
5032+ snapshot_dir=" $snapshot_base /$target "
5033+ fi
5034+
5035+ if [ -z " $snapshot_dir " ] || [ ! -d " $snapshot_dir " ]; then
5036+ error " Snapshot not found: $target "
5037+ echo " "
5038+ echo " Use 'crab rewind $num ' to see available snapshots"
5039+ exit 1
5040+ fi
5041+
5042+ local snapshot_name=$( basename " $snapshot_dir " )
5043+ local timestamp=$( echo " $snapshot_name " | cut -d' -' -f1)
5044+ local label=$( echo " $snapshot_name " | cut -d' -' -f2-)
5045+ local time_str=$( date -r " $timestamp " " +%Y-%m-%d %H:%M:%S" 2> /dev/null || date -d " @$timestamp " " +%Y-%m-%d %H:%M:%S" 2> /dev/null || echo " unknown" )
5046+
5047+ echo -e " ${CYAN} Rewinding workspace $num to: $label ($time_str )${NC} "
5048+ echo " "
5049+
5050+ # First, save current state as a snapshot
5051+ echo " 💾 Saving current state before rewind..."
5052+ create_snapshot " $num " " pre-rewind" > /dev/null
5053+
5054+ cd " $dir "
5055+
5056+ # Restore branch
5057+ if [ -f " $snapshot_dir /branch" ]; then
5058+ local branch=$( cat " $snapshot_dir /branch" )
5059+ echo " 🔀 Switching to branch: $branch "
5060+ git checkout " $branch " 2> /dev/null || git checkout -b " $branch " 2> /dev/null || true
5061+ fi
5062+
5063+ # Restore commit
5064+ if [ -f " $snapshot_dir /commit" ]; then
5065+ local commit=$( cat " $snapshot_dir /commit" )
5066+ echo " 📌 Resetting to commit: ${commit: 0: 8} "
5067+ git reset --hard " $commit " 2> /dev/null || true
5068+ fi
5069+
5070+ # Apply patches
5071+ if [ -f " $snapshot_dir /staged.patch" ] && [ -s " $snapshot_dir /staged.patch" ]; then
5072+ echo " 📄 Applying staged changes..."
5073+ git apply --index " $snapshot_dir /staged.patch" 2> /dev/null || true
5074+ fi
5075+
5076+ if [ -f " $snapshot_dir /unstaged.patch" ] && [ -s " $snapshot_dir /unstaged.patch" ]; then
5077+ echo " 📄 Applying unstaged changes..."
5078+ git apply " $snapshot_dir /unstaged.patch" 2> /dev/null || true
5079+ fi
5080+
5081+ echo " "
5082+ success " Rewound to: $label "
5083+ echo " "
5084+ echo -e " ${GRAY} Pre-rewind state saved. Undo with: crab rewind $num 1${NC} "
5085+ }
5086+
5087+ # Auto-snapshot daemon (background)
5088+ start_snapshot_daemon () {
5089+ local num=" $1 "
5090+ local interval=" ${2:- $SNAPSHOT_INTERVAL } "
5091+
5092+ echo -e " ${CYAN} Starting snapshot daemon for workspace $num ...${NC} "
5093+ echo " Interval: ${interval} s"
5094+ echo " "
5095+
5096+ while true ; do
5097+ create_snapshot " $num " " auto" > /dev/null 2>&1
5098+ sleep " $interval "
5099+ done
5100+ }
5101+
5102+ # Handle time travel commands
5103+ handle_rewind_command () {
5104+ load_config
5105+ validate_config
5106+
5107+ local num=" ${1:- } "
5108+
5109+ # Auto-detect workspace if not specified
5110+ if [ -z " $num " ] || ! [[ " $num " =~ ^[0-9]+$ ]]; then
5111+ num=$( detect_workspace)
5112+ if [ -z " $num " ]; then
5113+ error " Specify workspace number: crab rewind <N>"
5114+ exit 1
5115+ fi
5116+ # First arg might be the target, shift it
5117+ if [ -n " $1 " ] && ! [[ " $1 " =~ ^[0-9]+$ ]]; then
5118+ set -- " $num " " $@ "
5119+ fi
5120+ fi
5121+
5122+ local action=" ${2:- } "
5123+
5124+ case " $action " in
5125+ " " )
5126+ list_snapshots " $num "
5127+ ;;
5128+ " save" |" snapshot" )
5129+ save_snapshot " $num " " ${3:- manual} "
5130+ ;;
5131+ " daemon" |" auto" )
5132+ start_snapshot_daemon " $num " " ${3:- $SNAPSHOT_INTERVAL } "
5133+ ;;
5134+ * )
5135+ rewind_to_snapshot " $num " " $action "
5136+ ;;
5137+ esac
5138+ }
5139+
48365140# =============================================================================
48375141# Help / Cheat Sheet
48385142# =============================================================================
@@ -4970,6 +5274,16 @@ show_cheat() {
49705274║ ║
49715275╠═══════════════════════════════════════════════════════════════════════════════╣
49725276║ ║
5277+ ║ TIME TRAVEL (crab rewind ...) ║
5278+ ║ ──────────────────────────────────────────────────────────────────────── ║
5279+ ║ crab rewind <N> List snapshots for workspace N ║
5280+ ║ crab rewind <N> 3 Restore 3rd most recent snapshot ║
5281+ ║ crab rewind <N> 2h Restore from ~2 hours ago ║
5282+ ║ crab rewind <N> save Create manual snapshot ║
5283+ ║ crab snapshot Create snapshot of current workspace ║
5284+ ║ ║
5285+ ╠═══════════════════════════════════════════════════════════════════════════════╣
5286+ ║ ║
49735287║ TMUX KEYBINDINGS (Prefix = Ctrl+a) ║
49745288║ ──────────────────────────────────────────────────────────────────────── ║
49755289║ Option+1,2,3... Switch to workspace 1, 2, 3... ║
@@ -5330,6 +5644,19 @@ main() {
53305644 " handoff" )
53315645 handle_handoff_command " ${@: 2} "
53325646 ;;
5647+ " rewind" |" timetravel" |" tt" )
5648+ handle_rewind_command " ${@: 2} "
5649+ ;;
5650+ " snapshot" )
5651+ load_config
5652+ validate_config
5653+ local num=$( detect_workspace)
5654+ if [ -z " $num " ]; then
5655+ error " Not in a workspace. Use: crab snapshot from workspace directory"
5656+ exit 1
5657+ fi
5658+ save_snapshot " $num " " ${2:- manual} "
5659+ ;;
53335660 " receive" )
53345661 load_config
53355662 validate_config
0 commit comments