Building a Smart Session Tracker for Your Mac's Menu Bar

Ravgeet DhillonRavgeet Dhillon
10 min read

Picture this: You sit down at your Mac with a coffee, planning to "quickly check a few emails." Next thing you know, it's 3 PM, your coffee has achieved room temperature, and you're wondering if you've entered some sort of time vortex. Sound familiar?

If you're nodding your head (and possibly rubbing your stiff neck), you're not alone. In our hyper-connected world, time has a sneaky way of slipping through our fingers like sand – or like that last slice of pizza when you're not paying attention.

That's why I built a session tracker that lives right in your Mac's menu bar. It's like having a gentle, persistent friend who reminds you to take breaks, tracks your work patterns, and occasionally judges your life choices (in the nicest possible way).

What Does This Digital Time Wizard Do?

Our session tracker is basically a sophisticated time-keeping ninja that:

  1. Tracks Your Current Work Session - It knows when you start working and keeps a running timer

  2. Detects Sleep/Wake Cycles - Smartly figures out when you've been away from your computer

  3. Logs Daily Activity - Keeps a record of all your work sessions throughout the day

  4. Sends Gentle Reminders - Politely suggests you take a break after an hour (because your eyes and back will thank you)

  5. Shows Beautiful Stats - Displays your current session time and daily totals right in your menu bar

Think of it as a Fitbit for your productivity, but instead of counting steps, it's counting the minutes you spend glued to your screen.

The Magic Behind the Curtain

This isn't just any ordinary timer script – it's a surprisingly sophisticated piece of bash wizardry that handles all sorts of edge cases:

Smart Session Detection

The script doesn't just start counting from when you run it. It's smart enough to:

  • Detect when your Mac has been rebooted

  • Figure out when you've been away (sleeping, lunch break, or that inevitable YouTube rabbit hole)

  • Resume tracking seamlessly when you return

Intelligent Time Gap Detection

Here's where it gets clever: the script monitors for gaps in activity longer than 2 minutes. If it detects you've been away, it logs your previous session and starts a new one. It's like having a personal assistant who's really good at reading between the lines.

Cross-Reboot Persistence

Even if you restart your Mac, the script remembers your previous session and can estimate when it ended. It's like having a time-tracking elephant – it never forgets.

Setting Up Your New Digital Productivity Buddy

Ready to get this bad boy running on your Mac? Here's how to set it up:

Prerequisites

First, you'll need xbar (formerly BitBar), which is a fantastic tool that lets you put the output of any script in your Mac's menu bar:

# Install xbar using Homebrew
brew install xbar

If you don't have Homebrew installed, grab it from brew.sh first.

For prettier notifications, install terminal-notifier:

brew install terminal-notifier

Don't worry if you skip this – the script will fall back to macOS's built-in notification system.

Installing the Script

  1. Create the xbar plugins directory (if it doesn't exist):
mkdir -p "~/Library/ApplicationSupport/xbar/plugins"
  1. Create the script file:
nano "~/Library/Application Support/xbar/plugins/current-session.1m.sh"
  1. Copy and paste the following code:
#!/bin/bash

# Set PATH to include common locations
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"

# File to store the session start time
SESSION_FILE="/tmp/current_session_start"
# File to store daily activity log
DAILY_LOG_FILE="/tmp/daily_activity_$(date +%Y%m%d)"

# Get current time
CURRENT_TIME=$(date +%s)

# Check if system was recently awakened by looking at uptime vs session file age
UPTIME_SECONDS=$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//')
BOOT_TIME=$(date -r "$UPTIME_SECONDS" +%s 2>/dev/null || echo "$CURRENT_TIME")

# If session file doesn't exist or is older than boot time, create new session
if [ ! -f "$SESSION_FILE" ]; then
  SESSION_START="$CURRENT_TIME"
  SESSION_FILE_NEEDS_UPDATE=true
else
  STORED_TIME=$(cat "$SESSION_FILE")

  # Check if the stored time is valid (numeric and reasonable)
  if ! [[ "$STORED_TIME" =~ ^[0-9]+$ ]] || [ "$STORED_TIME" -lt 1000000000 ] || [ "$STORED_TIME" -gt $((CURRENT_TIME + 86400)) ]; then
    # Invalid timestamp, start new session
    SESSION_START="$CURRENT_TIME"
    SESSION_FILE_NEEDS_UPDATE=true
  elif [ "$STORED_TIME" -lt "$BOOT_TIME" ]; then
    # Session file is from before last boot, start new session
    SESSION_START="$CURRENT_TIME"
    SESSION_FILE_NEEDS_UPDATE=true
  else
    # Check if we've been asleep (gap in timestamps)
    LAST_CHECK_FILE="/tmp/last_session_check"
    if [ -f "$LAST_CHECK_FILE" ]; then
      LAST_CHECK=$(cat "$LAST_CHECK_FILE")
      TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

      # If gap is more than 2 minutes, assume we were asleep and start new session
      if [ "$TIME_GAP" -gt 120 ]; then
        SESSION_START="$CURRENT_TIME"
        SESSION_FILE_NEEDS_UPDATE=true
      else
        SESSION_START="$STORED_TIME"
        SESSION_FILE_NEEDS_UPDATE=false
      fi
    else
      SESSION_START="$STORED_TIME"
      SESSION_FILE_NEEDS_UPDATE=false
    fi
  fi
fi

# Track daily activity - check if we need to log previous session before updating files
LAST_CHECK_FILE="/tmp/last_session_check"
NEW_SESSION_STARTED=false

# Check if we're about to start a new session and need to log the previous one
if [ -f "$LAST_CHECK_FILE" ] && [ -f "$SESSION_FILE" ]; then
  LAST_CHECK=$(cat "$LAST_CHECK_FILE")
  PREV_SESSION_START=$(cat "$SESSION_FILE")
  TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

  # If gap detected and we have a valid previous session, log it before starting new session
  if [ "$TIME_GAP" -gt 120 ] && [ "$PREV_SESSION_START" -lt "$LAST_CHECK" ]; then
    PREV_SESSION_DURATION=$((LAST_CHECK - PREV_SESSION_START))
    # Only log sessions longer than 1 minute
    if [ "$PREV_SESSION_DURATION" -gt 60 ]; then
      echo "$PREV_SESSION_START $LAST_CHECK $PREV_SESSION_DURATION" >> "$DAILY_LOG_FILE"
    fi
    NEW_SESSION_STARTED=true
  fi
elif [ ! -f "$LAST_CHECK_FILE" ] && [ -f "$SESSION_FILE" ]; then
  # First run after boot - check if we should log a session from before reboot
  PREV_SESSION_START=$(cat "$SESSION_FILE")
  if [ "$PREV_SESSION_START" -lt "$BOOT_TIME" ]; then
    # Session was from before boot, try to estimate when it ended (use boot time)
    PREV_SESSION_DURATION=$((BOOT_TIME - PREV_SESSION_START))
    if [ "$PREV_SESSION_DURATION" -gt 60 ] && [ "$PREV_SESSION_DURATION" -lt 86400 ]; then
      # Only log if duration seems reasonable (between 1 minute and 24 hours)
      echo "$PREV_SESSION_START $BOOT_TIME $PREV_SESSION_DURATION" >> "$DAILY_LOG_FILE"
    fi
  fi
fi

# Update session file if we're starting a new session
if [ "$SESSION_FILE_NEEDS_UPDATE" = true ]; then
  echo "$SESSION_START" > "$SESSION_FILE"
  # Reset notification tracking for new session
  rm -f "/tmp/last_rest_notification"
fi

# Update the last check time
echo "$CURRENT_TIME" > "/tmp/last_session_check"

# Calculate the session duration in seconds
SESSION_DURATION=$((CURRENT_TIME - SESSION_START))

# Check if we need to show a rest notification (every hour)
NOTIFICATION_FILE="/tmp/last_rest_notification"
if [ "$SESSION_DURATION" -gt 3600 ]; then
  # Check if we've already shown a notification for this hour
  CURRENT_HOUR=$((SESSION_DURATION / 3600))
  if [ -f "$NOTIFICATION_FILE" ]; then
    LAST_NOTIFICATION_HOUR=$(cat "$NOTIFICATION_FILE")
  else
    LAST_NOTIFICATION_HOUR=0
  fi

  # Show notification if we haven't shown one for this hour yet
  if [ "$CURRENT_HOUR" -gt "$LAST_NOTIFICATION_HOUR" ]; then
    # Check if terminal-notifier is available
    if command -v terminal-notifier >/dev/null 2>&1; then
      terminal-notifier -title "Session Tracker" -subtitle "Time for a rest" -message "You've been working for ${CURRENT_HOUR} hour(s). Consider taking a break!" -sound funk -group "session-tracker"
    else
      # Fallback to osascript if terminal-notifier is not available
      osascript -e "display notification \"You've been working for ${CURRENT_HOUR} hour(s). Consider taking a break!\" with title \"Session Tracker\" subtitle \"Time for a rest\" sound name \"Glass\""
    fi
    echo "$CURRENT_HOUR" > "$NOTIFICATION_FILE"
  fi
fi

# Calculate total daily activity
TOTAL_TODAY=0
if [ -f "$DAILY_LOG_FILE" ]; then
  while read -r start_time end_time duration; do
    if [ -n "$duration" ] && [ "$duration" -gt 0 ]; then
      TOTAL_TODAY=$((TOTAL_TODAY + duration))
    fi
  done < "$DAILY_LOG_FILE"
fi

# Add current session to today's total
TOTAL_TODAY=$((TOTAL_TODAY + SESSION_DURATION))

# Convert duration to human-readable format
DURATION_HOURS=$((SESSION_DURATION / 3600))
DURATION_MINUTES=$(( (SESSION_DURATION % 3600) / 60 ))
DURATION_SECONDS=$((SESSION_DURATION % 60))

# Convert total daily time to human-readable format
TOTAL_HOURS=$((TOTAL_TODAY / 3600))
TOTAL_MINUTES=$(( (TOTAL_TODAY % 3600) / 60 ))

# Format the session output
if [ $DURATION_HOURS -gt 0 ]; then
  DURATION_STRING="${DURATION_HOURS}h ${DURATION_MINUTES}m"
else
  DURATION_STRING="${DURATION_MINUTES}m"
fi

# Format the daily total output
if [ $TOTAL_HOURS -gt 0 ]; then
  TOTAL_STRING="${TOTAL_HOURS}h ${TOTAL_MINUTES}m"
else
  TOTAL_STRING="${TOTAL_MINUTES}m"
fi

# Output the session duration and daily total
# Add warning indicator if session is over 1 hour
if [ "$SESSION_DURATION" -gt 3600 ]; then
  echo "⚠️ $DURATION_STRING | size=10"
else
  echo "🕒 $DURATION_STRING | size=10"
fi
echo "---"
echo "📊 Current Session: $DURATION_STRING"
echo "📅 Today Total: $TOTAL_STRING"

# Show session breakdown if there are previous sessions
if [ -f "$DAILY_LOG_FILE" ] && [ -s "$DAILY_LOG_FILE" ]; then
  SESSION_COUNT=$(wc -l < "$DAILY_LOG_FILE")
  echo "🔢 Sessions Today: $((SESSION_COUNT + 1))"
fi

# Add rest reminder in dropdown if session is over 1 hour
if [ "$SESSION_DURATION" -gt 3600 ]; then
  echo "---"
  echo "⏰ Take a break! You've been working for over an hour | color=orange"
fi
  1. Make it executable:
chmod +x "~/Library/Application Support/xbar/plugins/current-session.1m.sh"
  1. Start xbar and refresh:

    • Launch xbar from your Applications folder

    • Click the xbar icon in your menu bar and select "Refresh all"

Understanding the Code: A Gentle Journey Through Bash Land

Let's break down what this script does, step by step:

The Setup Phase

SESSION_FILE="/tmp/current_session_start"
DAILY_LOG_FILE="/tmp/daily_activity_$(date +%Y%m%d)"

We store our session data in temporary files. The daily log file includes the date, so each day gets its own log file. It's like having a new diary page for each day!

The Detective Work

UPTIME_SECONDS=$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//')
BOOT_TIME=$(date -r "$UPTIME_SECONDS" +%s 2>/dev/null || echo "$CURRENT_TIME")

This is where we get all CSI about when your Mac was last booted. We use this to figure out if your session file is from before a reboot.

The Smart Session Logic

The script has several scenarios it handles:

  1. First run ever: Creates a new session

  2. File exists but invalid: Starts fresh (protects against corrupted data)

  3. File is from before reboot: Starts a new session post-reboot

  4. Checking for sleep gaps: If there's a gap > 2 minutes, it assumes you were away

The Notification System

if [ "$SESSION_DURATION" -gt 3600 ]; then
  # Time for a break notification logic
fi

After an hour of work, the script gently reminds you to take a break. It's like having a caring friend who's also really good at math.

What You'll See in Action

Once everything is running, you'll see:

  • 🕒 45m in your menu bar (showing your current session time)

  • ⚠️ 1h 23m when you've been working for over an hour (subtle hint to take a break)

  • A dropdown menu showing:

    • 📊 Current Session: 1h 23m

    • 📅 Today Total: 3h 45m

    • 🔢 Sessions Today: 4

    • ⏰ Take a break! reminder (if you've been working too long)

The Beauty of Simplicity

What I love about this solution is that it's:

  • Lightweight: Uses minimal system resources

  • Unobtrusive: Sits quietly in your menu bar

  • Smart: Handles edge cases you didn't even think about

  • Customizable: Easy to modify for your specific needs

Customization Ideas

Want to make it your own? Here are some ideas:

  1. Change the break reminder interval: Modify the 3600 seconds (1 hour) to whatever works for you

  2. Add different notification sounds: Change the sound funk to sound glass, sound ping, etc.

  3. Modify the time gap detection: Change the 120 seconds gap threshold

  4. Add more detailed logging: Include timestamps, session names, or project tags

The Philosophical Side: Why This Matters

In our always-on world, this simple script serves a deeper purpose. It's not just about tracking time – it's about being mindful of how we spend our most precious resource. By making our work patterns visible, we can:

  • Recognize unhealthy patterns (like those 4-hour coding binges)

  • Celebrate productivity (look at all those completed sessions!)

  • Build better habits (those break reminders really do help)

  • Understand our rhythms (maybe you're most productive in the morning?)

Wrapping Up

Building this session tracker was a fun exercise in bash scripting and practical problem-solving. It started as a simple "how long have I been working?" question and evolved into a surprisingly sophisticated time-tracking system.

The best part? It's taught me to actually take breaks. Turns out, when your computer politely suggests you step away from the screen, you're more likely to listen than when your back is screaming at you.

So go ahead, give it a try! Your future self (and your neck) will thank you. And who knows? You might just discover that you're either more or less productive than you thought. Either way, at least you'll know for sure.

0
Subscribe to my newsletter

Read articles from Ravgeet Dhillon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ravgeet Dhillon
Ravgeet Dhillon

I am a full-time Software Engineer and Technical Content Writer. I code and write about React, Vue, Flutter, Laravel, Node, Strapi, and Python. I'm based in India🇮🇳 and currently work remotely for CloudAnswers. I also author technical articles for tech startups that include Strapi, Twilio, CircleCI, Lightrun, CSS Tricks, Draft.dev and many other tech companies.