Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions scripts/lib/date-compat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Cross-platform date utilities for CodeSensei
# Supports: GNU date (Linux), BSD date (macOS), python3 fallback

# Get today's date in YYYY-MM-DD format (UTC)
date_today() {
date -u '+%Y-%m-%d' 2>/dev/null || python3 -c "from datetime import datetime; print(datetime.utcnow().strftime('%Y-%m-%d'))"
}

# Get yesterday's date in YYYY-MM-DD format (UTC)
date_yesterday() {
# Try GNU date first
date -u -d 'yesterday' '+%Y-%m-%d' 2>/dev/null && return
# Try BSD date (macOS)
date -u -v-1d '+%Y-%m-%d' 2>/dev/null && return
# Python fallback
python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d'))" 2>/dev/null && return
# Last resort: empty string
echo ""
}

# Convert date string (YYYY-MM-DD) to epoch seconds
date_to_epoch() {
local date_str="$1"
# Try GNU date
date -u -d "$date_str" '+%s' 2>/dev/null && return
# Try BSD date (macOS)
date -u -j -f '%Y-%m-%d' "$date_str" '+%s' 2>/dev/null && return
# Python fallback
python3 -c "from datetime import datetime; print(int(datetime.strptime('$date_str', '%Y-%m-%d').timestamp()))" 2>/dev/null && return
echo "0"
}

# Get date N days ago in YYYY-MM-DD format (UTC)
date_days_ago() {
local days="$1"
# Try GNU date
date -u -d "${days} days ago" '+%Y-%m-%d' 2>/dev/null && return
# Try BSD date (macOS)
date -u -v-${days}d '+%Y-%m-%d' 2>/dev/null && return
# Python fallback
python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=${days})).strftime('%Y-%m-%d'))" 2>/dev/null && return
echo ""
}

# Get current UTC timestamp in ISO format
date_now_iso() {
date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || python3 -c "from datetime import datetime; print(datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'))"
}
12 changes: 8 additions & 4 deletions scripts/quiz-selector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ PROFILE_FILE="$PROFILE_DIR/profile.json"
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
QUIZ_BANK="$PLUGIN_ROOT/data/quiz-bank.json"

# Source cross-platform date helpers
# shellcheck source=scripts/lib/date-compat.sh
source "$PLUGIN_ROOT/scripts/lib/date-compat.sh"

# Default output if we can't determine anything
DEFAULT_OUTPUT='{"mode":"dynamic","concept":null,"reason":"No profile data available","static_question":null,"belt":"white","quiz_format":"multiple_choice"}'

Expand All @@ -40,8 +44,8 @@ SESSION_CONCEPTS=$(jq -c '.session_concepts // []' "$PROFILE_FILE")
TOTAL_QUIZZES=$(jq -r '.quizzes.total // 0' "$PROFILE_FILE")
CORRECT_QUIZZES=$(jq -r '.quizzes.correct // 0' "$PROFILE_FILE")

TODAY=$(date -u +%Y-%m-%d)
NOW_EPOCH=$(date +%s)
TODAY=$(date_today)
NOW_EPOCH=$(date_to_epoch "$TODAY")

# Determine quiz format based on belt level
# Orange Belt+ gets a mix of formats; lower belts get multiple choice
Expand Down Expand Up @@ -84,8 +88,8 @@ if [ "$QUIZ_HISTORY" != "[]" ]; then

# Calculate days since last wrong answer
LAST_WRONG_DATE=$(echo "$LAST_WRONG" | cut -d'T' -f1)
LAST_EPOCH=$(date -j -f "%Y-%m-%d" "$LAST_WRONG_DATE" +%s 2>/dev/null || date -d "$LAST_WRONG_DATE" +%s 2>/dev/null)
if [ -n "$LAST_EPOCH" ]; then
LAST_EPOCH=$(date_to_epoch "$LAST_WRONG_DATE")
if [ -n "$LAST_EPOCH" ] && [ "$LAST_EPOCH" != "0" ]; then
DAYS_SINCE=$(( (NOW_EPOCH - LAST_EPOCH) / 86400 ))
else
DAYS_SINCE=999
Expand Down
16 changes: 11 additions & 5 deletions scripts/session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
PROFILE_DIR="$HOME/.code-sensei"
PROFILE_FILE="$PROFILE_DIR/profile.json"
SESSION_LOG="$PROFILE_DIR/sessions.log"
TODAY=$(date -u +%Y-%m-%d)
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"

# Source cross-platform date helpers
# shellcheck source=scripts/lib/date-compat.sh
source "$PLUGIN_ROOT/scripts/lib/date-compat.sh"

TODAY=$(date_today)

# Create profile directory if it doesn't exist
mkdir -p "$PROFILE_DIR"
Expand All @@ -17,7 +23,7 @@ if [ ! -f "$PROFILE_FILE" ]; then
"version": "1.0.0",
"plugin": "code-sensei",
"brand": "Dojo Coding",
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"created_at": "$(date_now_iso)",
"belt": "white",
"xp": 0,
"streak": {
Expand Down Expand Up @@ -45,7 +51,7 @@ if [ ! -f "$PROFILE_FILE" ]; then
"id": "first-session",
"name": "First Steps",
"description": "Started your first CodeSensei session",
"earned_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
"earned_at": "$(date_now_iso)"
}
],
"preferences": {
Expand Down Expand Up @@ -73,7 +79,7 @@ if command -v jq &> /dev/null; then
NEW_STREAK=$CURRENT_STREAK
elif [ -n "$LAST_SESSION" ]; then
# Check if last session was yesterday
YESTERDAY=$(date -u -d "yesterday" +%Y-%m-%d 2>/dev/null || date -u -v-1d +%Y-%m-%d 2>/dev/null)
YESTERDAY=$(date_yesterday)
if [ "$LAST_SESSION" = "$YESTERDAY" ]; then
NEW_STREAK=$((CURRENT_STREAK + 1))
else
Expand Down Expand Up @@ -107,7 +113,7 @@ if command -v jq &> /dev/null; then
echo "$UPDATED" > "$PROFILE_FILE"

# Log session
echo "$TODAY $(date -u +%H:%M:%S) session_start" >> "$SESSION_LOG"
echo "$(date_now_iso) session_start" >> "$SESSION_LOG"

# Show streak info if notable
BELT=$(jq -r '.belt // "white"' "$PROFILE_FILE")
Expand Down