From 08eeee54a684ee0099969c7f2674388c3adf05c1 Mon Sep 17 00:00:00 2001 From: alexzarp Date: Tue, 21 Jul 2026 17:23:10 -0300 Subject: [PATCH] feat: add configurable Claude model and effort parameters, default is haiku effort low --- README.md | 23 ++++- claude-auto-renew-advanced.sh | 59 ++++++----- claude-auto-renew-daemon.sh | 137 ++++++++++++++----------- claude-auto-renew.sh | 27 ++--- claude-daemon-manager.sh | 187 +++++++++++++++++++++------------- 5 files changed, 255 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index 0f4ce3b2..b238ed56 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Claude Code operates on a 5-hour subscription model that renews from your first **Solution:** CC AutoRenew prevents both gaps AND session burning: - 🚫 **Prevents Gaps** - Automatically starts new sessions when blocks expire -- ⏰ **Prevents Session Burning** - Schedule when monitoring begins (`--at "09:00"`) +- ⏰ **Prevents Session Burning** - Schedule when monitoring begins (`--at "09:00"`) - 🎯 **Perfect Timing** - Start your 5-hour block exactly when you need it ## ✨ Features @@ -27,6 +27,7 @@ Claude Code operates on a 5-hour subscription model that renews from your first - 📝 **Detailed Logging** - Track all renewal activities with WAITING/ACTIVE/STOPPED states - 📊 **Live Dashboard** - Real-time monitoring with progress bars and renewal schedules - 💬 **Custom Messages** - Use `--message` to send contextual renewal messages instead of generic greetings +- 💸 **Economical Defaults** - Uses a low-effort Claude session and a configurable model via `CLAUDE_MODEL` - 🛡️ **Failsafe Design** - Multiple fallback mechanisms and prevents renewals near stop time - 🖥️ **Cross-platform** - Works on macOS and Linux - ⚡ **Clock-only Mode** - Use `--disableccusage` flag to bypass ccusage entirely @@ -159,7 +160,7 @@ The new live dashboard provides real-time monitoring of your Claude renewal stat **Progress Bar Colors:** - 🟢 **Green** - More than 1 hour remaining -- 🟡 **Yellow** - 30-60 minutes remaining +- 🟡 **Yellow** - 30-60 minutes remaining - 🔴 **Red** - Less than 30 minutes remaining **Usage:** @@ -202,6 +203,18 @@ Example dashboard output: 6. **Automatically restarts** the next day at start time 7. **Logs** all activities for transparency +### Economical Mode + +By default, the daemon uses a low-effort Claude session and a model set through `CLAUDE_MODEL`. The scripts default that variable to `haiku`, but you can override both at startup through command flags: + +```bash +./claude-daemon-manager.sh start --model haiku --effort low +``` + +The selected values persist across restarts until you change them again. + +If your Claude CLI/account exposes a cheaper model alias, pass it with `--model` and it will be used for every renewal. + ### Custom Renewal Messages 💬 **Default Behavior (without --message):** The daemon automatically sends random greetings ("hi", "hello", "hey there", "good day", "greetings", "howdy", "what's up", "salutations") when renewing sessions. This is the original behavior and requires no configuration. @@ -285,7 +298,7 @@ This mode is useful when: The daemon adjusts its checking frequency based on time remaining: - **Normal**: Every 10 minutes -- **< 30 minutes**: Every 2 minutes +- **< 30 minutes**: Every 2 minutes - **< 5 minutes**: Every 30 seconds - **After renewal**: 5-minute cooldown @@ -347,7 +360,7 @@ The daemon uses smart defaults, but you can modify behavior by editing `claude-a ```bash # Adjust check intervals (in seconds) - Normal: 600 (10 minutes) -- Approaching: 120 (2 minutes) +- Approaching: 120 (2 minutes) - Imminent: 30 (30 seconds) ``` @@ -434,7 +447,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - Can be added to system startup for automatic launch --- -## Buy me a coffee if you like my work: +## Buy me a coffee if you like my work: Buy Me A Coffee -- diff --git a/claude-auto-renew-advanced.sh b/claude-auto-renew-advanced.sh index 09a6db7d..ab420206 100755 --- a/claude-auto-renew-advanced.sh +++ b/claude-auto-renew-advanced.sh @@ -5,6 +5,8 @@ LOG_FILE="$HOME/.claude-auto-renew.log" LAST_ACTIVITY_FILE="$HOME/.claude-last-activity" +CLAUDE_MODEL="${CLAUDE_MODEL:-haiku}" +CLAUDE_EFFORT="${CLAUDE_EFFORT:-low}" # Function to log messages log_message() { @@ -31,19 +33,19 @@ parse_time_remaining() { log_message "ERROR: ccusage not available" return 1 fi - + # Try to get blocks info and extract time remaining local output=$($ccusage_cmd blocks 2>/dev/null | grep -i "time remaining" | head -1) - + if [ -z "$output" ]; then # Try live mode for more accurate info output=$($ccusage_cmd blocks --live 2>/dev/null | grep -i "remaining" | head -1) fi - + # Extract hours and minutes from various formats local hours=0 local minutes=0 - + if [[ "$output" =~ ([0-9]+)h[[:space:]]*([0-9]+)m ]]; then hours=${BASH_REMATCH[1]} minutes=${BASH_REMATCH[2]} @@ -53,7 +55,7 @@ parse_time_remaining() { elif [[ "$output" =~ ([0-9]+)m ]]; then minutes=${BASH_REMATCH[1]} fi - + # Convert to total minutes local total_minutes=$((hours * 60 + minutes)) echo "$total_minutes" @@ -63,10 +65,10 @@ parse_time_remaining() { should_start_session() { # First try to get accurate time from ccusage local minutes_remaining=$(parse_time_remaining) - + if [ -n "$minutes_remaining" ] && [ "$minutes_remaining" -gt 0 ]; then log_message "Time remaining in current block: $minutes_remaining minutes" - + # Start session if less than 5 minutes remaining if [ "$minutes_remaining" -lt 5 ]; then log_message "Reset window approaching, preparing to start session" @@ -75,13 +77,13 @@ should_start_session() { return 1 fi fi - + # Fallback: Check last activity time if [ -f "$LAST_ACTIVITY_FILE" ]; then local last_activity=$(cat "$LAST_ACTIVITY_FILE") local current_time=$(date +%s) local time_diff=$((current_time - last_activity)) - + # If more than 5 hours (18000 seconds) have passed if [ $time_diff -gt 18000 ]; then log_message "More than 5 hours since last activity, starting session" @@ -89,7 +91,7 @@ should_start_session() { else local remaining=$((18000 - time_diff)) log_message "Fallback check: $((remaining / 60)) minutes until 5-hour mark" - + # Start if within 5 minutes of the 5-hour mark if [ $remaining -lt 300 ]; then return 0 @@ -100,25 +102,28 @@ should_start_session() { log_message "No previous activity found, starting session" return 0 fi - + return 1 } # Function to start Claude session start_claude_session() { log_message "Attempting to start Claude session" - + # Check if claude command exists if ! command -v claude &> /dev/null; then log_message "ERROR: claude command not found" return 1 fi - + + local claude_cmd=(claude --model "$CLAUDE_MODEL" --effort "$CLAUDE_EFFORT") + log_message "Using Claude model: $CLAUDE_MODEL (effort: $CLAUDE_EFFORT)" + # Create a temporary expect script for better automation - cat > /tmp/claude_auto_start.exp << 'EOF' + cat > /tmp/claude_auto_start.exp << EOF #!/usr/bin/expect -f set timeout 10 -spawn claude +spawn ${claude_cmd[*]} expect { ">" { send "hi\r" @@ -132,25 +137,25 @@ expect { } expect eof EOF - + chmod +x /tmp/claude_auto_start.exp - + # Try using expect if available if command -v expect &> /dev/null; then /tmp/claude_auto_start.exp >> "$LOG_FILE" 2>&1 local result=$? else # Fallback to simple echo with macOS-compatible timeout - (echo "hi" | claude >> "$LOG_FILE" 2>&1) & + (echo "hi" | "${claude_cmd[@]}" >> "$LOG_FILE" 2>&1) & local pid=$! - + # Wait up to 10 seconds local count=0 while kill -0 $pid 2>/dev/null && [ $count -lt 10 ]; do sleep 1 ((count++)) done - + # Kill if still running if kill -0 $pid 2>/dev/null; then kill $pid 2>/dev/null @@ -161,10 +166,10 @@ EOF local result=$? fi fi - + # Clean up rm -f /tmp/claude_auto_start.exp - + if [ $result -eq 0 ]; then log_message "Successfully started Claude session" date +%s > "$LAST_ACTIVITY_FILE" @@ -178,19 +183,19 @@ EOF # Main execution main() { log_message "=== Starting Claude auto-renewal check ===" - + # Check ccusage availability if ! get_ccusage_cmd &> /dev/null; then log_message "WARNING: ccusage not found. Install with: npm install -g ccusage" log_message "Falling back to time-based checking" fi - + # Check if we should start a session if should_start_session; then # Wait a moment to ensure we're in the renewal window log_message "Waiting 60 seconds to ensure renewal window..." sleep 60 - + # Try to start session up to 3 times local attempts=0 while [ $attempts -lt 3 ]; do @@ -203,14 +208,14 @@ main() { sleep 30 fi done - + if [ $attempts -eq 3 ]; then log_message "ERROR: Failed to start session after 3 attempts" fi else log_message "Not time for renewal yet" fi - + log_message "=== Check complete ===" } diff --git a/claude-auto-renew-daemon.sh b/claude-auto-renew-daemon.sh index 1126fda8..2e345471 100755 --- a/claude-auto-renew-daemon.sh +++ b/claude-auto-renew-daemon.sh @@ -10,6 +10,8 @@ START_TIME_FILE="$HOME/.claude-auto-renew-start-time" STOP_TIME_FILE="$HOME/.claude-auto-renew-stop-time" MESSAGE_FILE="$HOME/.claude-auto-renew-message" DISABLE_CCUSAGE=false +CLAUDE_MODEL="${CLAUDE_MODEL:-haiku}" +CLAUDE_EFFORT="${CLAUDE_EFFORT:-low}" # Function to log messages log_message() { @@ -31,15 +33,15 @@ is_monitoring_active() { local current_epoch=$(date +%s) local start_epoch="" local stop_epoch="" - + if [ -f "$START_TIME_FILE" ]; then start_epoch=$(cat "$START_TIME_FILE") fi - + if [ -f "$STOP_TIME_FILE" ]; then stop_epoch=$(cat "$STOP_TIME_FILE") fi - + # If no start time set, always active (unless stop time is set and passed) if [ -z "$start_epoch" ]; then if [ -n "$stop_epoch" ] && [ "$current_epoch" -ge "$stop_epoch" ]; then @@ -48,17 +50,17 @@ is_monitoring_active() { return 0 # Active fi fi - + # Check if we're before start time if [ "$current_epoch" -lt "$start_epoch" ]; then return 1 # Before start time fi - + # Check if we're past stop time if [ -n "$stop_epoch" ] && [ "$current_epoch" -ge "$stop_epoch" ]; then return 1 # Past stop time fi - + return 0 # In active window } @@ -67,15 +69,15 @@ should_restart_tomorrow() { if [ ! -f "$START_TIME_FILE" ] || [ ! -f "$STOP_TIME_FILE" ]; then return 1 # No scheduling needed fi - + local current_epoch=$(date +%s) local stop_epoch=$(cat "$STOP_TIME_FILE") - + # Check if we've passed stop time if [ "$current_epoch" -ge "$stop_epoch" ]; then return 0 # Should restart tomorrow fi - + return 1 # Not yet time } @@ -84,33 +86,33 @@ schedule_next_day_restart() { if [ ! -f "$START_TIME_FILE" ]; then return 1 fi - + local start_epoch=$(cat "$START_TIME_FILE") local stop_epoch="" - + if [ -f "$STOP_TIME_FILE" ]; then stop_epoch=$(cat "$STOP_TIME_FILE") fi - + # Calculate tomorrow's start time local next_start=$((start_epoch + 86400)) local next_stop="" - + if [ -n "$stop_epoch" ]; then next_stop=$((stop_epoch + 86400)) fi - + # Update the time files for tomorrow echo "$next_start" > "$START_TIME_FILE" if [ -n "$next_stop" ]; then echo "$next_stop" > "$STOP_TIME_FILE" fi - + # Remove activation marker so it gets recreated tomorrow rm -f "${START_TIME_FILE}.activated" 2>/dev/null - + log_message "🔄 Scheduled restart for tomorrow at $(date -d "@$next_start" 2>/dev/null || date -r "$next_start")" - + return 0 } @@ -120,11 +122,11 @@ get_time_until_start() { echo "0" return fi - + local start_epoch=$(cat "$START_TIME_FILE") local current_epoch=$(date +%s) local diff=$((start_epoch - current_epoch)) - + if [ "$diff" -le 0 ]; then echo "0" else @@ -151,45 +153,48 @@ get_minutes_until_reset() { if [ "$DISABLE_CCUSAGE" = true ]; then return 1 fi - + local ccusage_cmd=$(get_ccusage_cmd) if [ $? -ne 0 ]; then return 1 fi - + # Try to get time remaining from ccusage local output=$($ccusage_cmd blocks 2>/dev/null | grep -i "time remaining" | head -1) - + if [ -z "$output" ]; then output=$($ccusage_cmd blocks --live 2>/dev/null | grep -i "remaining" | head -1) fi - + # Parse time local hours=0 local minutes=0 - + if [[ "$output" =~ ([0-9]+)h[[:space:]]*([0-9]+)m ]]; then hours=${BASH_REMATCH[1]} minutes=${BASH_REMATCH[2]} elif [[ "$output" =~ ([0-9]+)m ]]; then minutes=${BASH_REMATCH[1]} fi - + echo $((hours * 60 + minutes)) } # Function to start Claude session start_claude_session() { log_message "Starting Claude session for renewal..." - + if ! command -v claude &> /dev/null; then log_message "ERROR: claude command not found" return 1 fi - + + local claude_cmd=(claude --model "$CLAUDE_MODEL" --effort "$CLAUDE_EFFORT") + log_message "Using Claude model: $CLAUDE_MODEL (effort: $CLAUDE_EFFORT)" + # Check if custom message is available local selected_message="" - + if [ -f "$MESSAGE_FILE" ]; then # Use custom message selected_message=$(cat "$MESSAGE_FILE") @@ -197,24 +202,24 @@ start_claude_session() { else # Define an array of predefined messages local messages=("hi" "hello" "hey there" "good day" "greetings" "howdy" "what's up" "salutations") - + # Randomly select a message from the array local random_index=$((RANDOM % ${#messages[@]})) selected_message="${messages[$random_index]}" fi - + # Simple approach - macOS compatible # Use a subshell with background process for timeout - (echo "$selected_message" | claude >> "$LOG_FILE" 2>&1) & + (echo "$selected_message" | "${claude_cmd[@]}" >> "$LOG_FILE" 2>&1) & local pid=$! - + # Wait up to 10 seconds local count=0 while kill -0 $pid 2>/dev/null && [ $count -lt 10 ]; do sleep 1 ((count++)) done - + # Kill if still running if kill -0 $pid 2>/dev/null; then kill $pid 2>/dev/null @@ -224,7 +229,7 @@ start_claude_session() { wait $pid local result=$? fi - + if [ $result -eq 0 ] || [ $result -eq 124 ]; then # 124 is timeout exit code log_message "Claude session started successfully with message: $selected_message" date +%s > "$LAST_ACTIVITY_FILE" @@ -238,10 +243,10 @@ start_claude_session() { # Function to calculate next check time calculate_sleep_duration() { local minutes_remaining=$(get_minutes_until_reset) - + if [ -n "$minutes_remaining" ] && [ "$minutes_remaining" -gt 0 ]; then log_message "Time remaining: $minutes_remaining minutes" - + if [ "$minutes_remaining" -le 5 ]; then # Check every 30 seconds when close to reset echo 30 @@ -259,7 +264,7 @@ calculate_sleep_duration() { local current_time=$(date +%s) local time_diff=$((current_time - last_activity)) local remaining=$((18000 - time_diff)) # 5 hours = 18000 seconds - + if [ "$remaining" -le 300 ]; then # 5 minutes echo 30 elif [ "$remaining" -le 1800 ]; then # 30 minutes @@ -287,21 +292,21 @@ main() { rm -f "$PID_FILE" fi fi - + # Save PID echo $$ > "$PID_FILE" - + log_message "=== Claude Auto-Renewal Daemon Started ===" log_message "PID: $$" log_message "Logs: $LOG_FILE" - + # Log ccusage status if [ "$DISABLE_CCUSAGE" = true ]; then log_message "⚠️ ccusage DISABLED - Using clock-based timing only" else log_message "✅ ccusage ENABLED - Using accurate timing when available" fi - + # Check for start and stop times if [ -f "$START_TIME_FILE" ]; then start_epoch=$(cat "$START_TIME_FILE") @@ -309,14 +314,14 @@ main() { else log_message "No start time set - will begin monitoring immediately" fi - + if [ -f "$STOP_TIME_FILE" ]; then stop_epoch=$(cat "$STOP_TIME_FILE") log_message "Stop time configured: $(date -d "@$stop_epoch" 2>/dev/null || date -r "$stop_epoch")" else log_message "No stop time set - will monitor continuously" fi - + # Check for custom message if [ -f "$MESSAGE_FILE" ]; then custom_message=$(cat "$MESSAGE_FILE") @@ -324,26 +329,26 @@ main() { else log_message "Using default random greeting messages for renewal" fi - + # Check ccusage availability if [ "$DISABLE_CCUSAGE" = false ] && ! get_ccusage_cmd &> /dev/null; then log_message "WARNING: ccusage not found. Using time-based checking." log_message "Install ccusage for more accurate timing: npm install -g ccusage" fi - + # Main loop while true; do # Check if we should schedule next day restart first if should_restart_tomorrow; then log_message "🛑 Stop time reached. Scheduling restart for tomorrow..." schedule_next_day_restart - + # Wait for tomorrow's start time while ! is_monitoring_active; do time_until_start=$(get_time_until_start) hours=$((time_until_start / 3600)) minutes=$(((time_until_start % 3600) / 60)) - + if [ "$hours" -gt 0 ]; then log_message "⏰ Waiting for tomorrow's start time (${hours}h ${minutes}m remaining)..." sleep 3600 # Check every hour when waiting for tomorrow @@ -352,11 +357,11 @@ main() { sleep 300 # Check every 5 minutes when close fi done - + log_message "🌅 New day started! Resuming monitoring..." continue fi - + # Check if we're in monitoring window if ! is_monitoring_active; then # Calculate time until start or reason for inactivity @@ -365,7 +370,7 @@ main() { hours=$((time_until_start / 3600)) minutes=$(((time_until_start % 3600) / 60)) seconds=$((time_until_start % 60)) - + if [ "$time_until_start" -gt 0 ]; then # Before start time if [ "$hours" -gt 0 ]; then @@ -393,7 +398,7 @@ main() { fi continue fi - + # If we just entered active time, log it if [ -f "$START_TIME_FILE" ]; then # Check if this is the first time we're active today @@ -402,15 +407,15 @@ main() { touch "${START_TIME_FILE}.activated" fi fi - + # Check if we're approaching stop time current_time=$(date +%s) stop_time_approaching=false - + if [ -f "$STOP_TIME_FILE" ]; then stop_epoch=$(cat "$STOP_TIME_FILE") time_until_stop=$((stop_epoch - current_time)) - + # Don't start new renewals if stop time is within 10 minutes if [ "$time_until_stop" -le 600 ] && [ "$time_until_stop" -gt 0 ]; then stop_time_approaching=true @@ -418,13 +423,13 @@ main() { log_message "⚠️ Stop time approaching in ${minutes_until_stop} minutes - no new renewals" fi fi - + # Get minutes until reset minutes_remaining=$(get_minutes_until_reset) - + # Check if we should renew (only if not approaching stop time) should_renew=false - + if [ "$stop_time_approaching" = false ]; then if [ -n "$minutes_remaining" ] && [ "$minutes_remaining" -gt 0 ]; then if [ "$minutes_remaining" -le 2 ]; then @@ -437,7 +442,7 @@ main() { last_activity=$(cat "$LAST_ACTIVITY_FILE") current_time=$(date +%s) time_diff=$((current_time - last_activity)) - + if [ $time_diff -ge 18000 ]; then should_renew=true log_message "5 hours elapsed since last activity, renewing..." @@ -449,12 +454,12 @@ main() { fi fi fi - + # Perform renewal if needed if [ "$should_renew" = true ]; then # Wait a bit to ensure we're in the renewal window sleep 60 - + # Try to start session if start_claude_session; then log_message "Renewal successful!" @@ -465,11 +470,11 @@ main() { sleep 60 fi fi - + # Calculate how long to sleep sleep_duration=$(calculate_sleep_duration) log_message "Next check in $((sleep_duration / 60)) minutes" - + # Sleep until next check sleep "$sleep_duration" done @@ -482,6 +487,14 @@ while [[ $# -gt 0 ]]; do DISABLE_CCUSAGE=true shift ;; + --model) + CLAUDE_MODEL="$2" + shift 2 + ;; + --effort) + CLAUDE_EFFORT="$2" + shift 2 + ;; *) shift ;; diff --git a/claude-auto-renew.sh b/claude-auto-renew.sh index 2d1f46ed..b2f01c82 100755 --- a/claude-auto-renew.sh +++ b/claude-auto-renew.sh @@ -5,6 +5,8 @@ # Configuration LOG_FILE="$HOME/.claude-auto-renew.log" +CLAUDE_MODEL="${CLAUDE_MODEL:-haiku}" +CLAUDE_EFFORT="${CLAUDE_EFFORT:-low}" # Function to log messages log_message() { @@ -15,32 +17,32 @@ log_message() { check_reset_window() { # Run ccusage to get current block info # We'll parse the output to determine if we should start a session - + # First, check if ccusage is available if ! command -v ccusage &> /dev/null && ! command -v bunx &> /dev/null; then log_message "ERROR: ccusage not found. Please install it first." return 1 fi - + # Get the current block information if command -v ccusage &> /dev/null; then BLOCK_INFO=$(ccusage blocks --json 2>/dev/null | jq -r '.current_block.time_remaining' 2>/dev/null) else BLOCK_INFO=$(bunx ccusage blocks --json 2>/dev/null | jq -r '.current_block.time_remaining' 2>/dev/null) fi - + # If we can't get block info, try alternative approach if [ -z "$BLOCK_INFO" ] || [ "$BLOCK_INFO" = "null" ]; then log_message "Could not get block info from ccusage, checking alternative method" - + # Check if there's been recent activity (within last 5 hours) LAST_ACTIVITY_FILE="$HOME/.claude-last-activity" - + if [ -f "$LAST_ACTIVITY_FILE" ]; then LAST_ACTIVITY=$(cat "$LAST_ACTIVITY_FILE") CURRENT_TIME=$(date +%s) TIME_DIFF=$((CURRENT_TIME - LAST_ACTIVITY)) - + # If more than 5 hours have passed, we should start a session if [ $TIME_DIFF -gt 18000 ]; then # 5 hours = 18000 seconds return 0 # Should start session @@ -54,7 +56,7 @@ check_reset_window() { return 0 fi fi - + # Parse time remaining (assuming format like "2h 30m" or minutes) # If time remaining is less than 10 minutes, we should prepare to start a new session if [[ "$BLOCK_INFO" =~ ([0-9]+)m$ ]]; then @@ -64,23 +66,24 @@ check_reset_window() { return 0 fi fi - + return 1 } # Function to start Claude session start_claude_session() { log_message "Starting Claude session to maintain renewal window" - + # Check if claude command exists if ! command -v claude &> /dev/null; then log_message "ERROR: claude command not found" return 1 fi - + # Start claude with a simple command that exits immediately - echo "hi" | claude 2>&1 >> "$LOG_FILE" - + log_message "Using Claude model: $CLAUDE_MODEL (effort: $CLAUDE_EFFORT)" + echo "hi" | claude --model "$CLAUDE_MODEL" --effort "$CLAUDE_EFFORT" >> "$LOG_FILE" 2>&1 + if [ $? -eq 0 ]; then log_message "Successfully started Claude session" # Update last activity time diff --git a/claude-daemon-manager.sh b/claude-daemon-manager.sh index 9d2c199f..3e055f7b 100755 --- a/claude-daemon-manager.sh +++ b/claude-daemon-manager.sh @@ -8,6 +8,8 @@ LOG_FILE="$HOME/.claude-auto-renew-daemon.log" START_TIME_FILE="$HOME/.claude-auto-renew-start-time" STOP_TIME_FILE="$HOME/.claude-auto-renew-stop-time" MESSAGE_FILE="$HOME/.claude-auto-renew-message" +MODEL_FILE="$HOME/.claude-auto-renew-model" +EFFORT_FILE="$HOME/.claude-auto-renew-effort" # Colors for output RED='\033[0;31m' @@ -33,7 +35,17 @@ start_daemon() { STOP_TIME="" DISABLE_CCUSAGE=false CUSTOM_MESSAGE="" - + CUSTOM_MODEL="" + CUSTOM_EFFORT="" + + if [ -f "$MODEL_FILE" ]; then + CUSTOM_MODEL=$(cat "$MODEL_FILE") + fi + + if [ -f "$EFFORT_FILE" ]; then + CUSTOM_EFFORT=$(cat "$EFFORT_FILE") + fi + # Parse parameters while [[ $# -gt 1 ]]; do case $2 in @@ -53,12 +65,20 @@ start_daemon() { CUSTOM_MESSAGE="$3" shift 2 ;; + --model) + CUSTOM_MODEL="$3" + shift 2 + ;; + --effort) + CUSTOM_EFFORT="$3" + shift 2 + ;; *) shift ;; esac done - + # Process start time if [ -n "$START_TIME" ]; then # Validate and convert start time to epoch @@ -66,15 +86,15 @@ start_daemon() { # Format: "HH:MM" - assume today START_TIME="$(date '+%Y-%m-%d') $START_TIME:00" fi - + # Convert to epoch timestamp START_EPOCH=$(date -d "$START_TIME" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S" "$START_TIME" +%s 2>/dev/null) - + if [ $? -ne 0 ]; then print_error "Invalid start time format. Use 'HH:MM' or 'YYYY-MM-DD HH:MM'" return 1 fi - + # Store start time echo "$START_EPOCH" > "$START_TIME_FILE" print_status "Daemon will start monitoring at: $(date -d "@$START_EPOCH" 2>/dev/null || date -r "$START_EPOCH")" @@ -82,7 +102,7 @@ start_daemon() { # Remove any existing start time (start immediately) rm -f "$START_TIME_FILE" 2>/dev/null fi - + # Process stop time if [ -n "$STOP_TIME" ]; then # Validate and convert stop time to epoch @@ -90,21 +110,21 @@ start_daemon() { # Format: "HH:MM" - assume today STOP_TIME="$(date '+%Y-%m-%d') $STOP_TIME:00" fi - + # Convert to epoch timestamp STOP_EPOCH=$(date -d "$STOP_TIME" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S" "$STOP_TIME" +%s 2>/dev/null) - + if [ $? -ne 0 ]; then print_error "Invalid stop time format. Use 'HH:MM' or 'YYYY-MM-DD HH:MM'" return 1 fi - + # Validate that stop time is after start time if [ -n "$START_EPOCH" ] && [ "$STOP_EPOCH" -le "$START_EPOCH" ]; then print_error "Stop time must be after start time" return 1 fi - + # Store stop time echo "$STOP_EPOCH" > "$STOP_TIME_FILE" print_status "Daemon will stop monitoring at: $(date -d "@$STOP_EPOCH" 2>/dev/null || date -r "$STOP_EPOCH")" @@ -112,7 +132,7 @@ start_daemon() { # Remove any existing stop time rm -f "$STOP_TIME_FILE" 2>/dev/null fi - + # Process custom message if [ -n "$CUSTOM_MESSAGE" ]; then # Store custom message @@ -122,7 +142,19 @@ start_daemon() { # Remove any existing custom message (use default messages) rm -f "$MESSAGE_FILE" 2>/dev/null fi - + + # Process custom model + if [ -n "$CUSTOM_MODEL" ]; then + echo "$CUSTOM_MODEL" > "$MODEL_FILE" + print_status "Using Claude model: $CUSTOM_MODEL" + fi + + # Process custom effort + if [ -n "$CUSTOM_EFFORT" ]; then + echo "$CUSTOM_EFFORT" > "$EFFORT_FILE" + print_status "Using Claude effort: $CUSTOM_EFFORT" + fi + if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") if kill -0 "$PID" 2>/dev/null; then @@ -130,16 +162,24 @@ start_daemon() { return 1 fi fi - + print_status "Starting Claude auto-renewal daemon..." + DAEMON_ARGS=() + if [ -n "$CUSTOM_MODEL" ]; then + DAEMON_ARGS+=(--model "$CUSTOM_MODEL") + fi + if [ -n "$CUSTOM_EFFORT" ]; then + DAEMON_ARGS+=(--effort "$CUSTOM_EFFORT") + fi if [ "$DISABLE_CCUSAGE" = true ]; then - nohup "$DAEMON_SCRIPT" --disableccusage > /dev/null 2>&1 & + DAEMON_ARGS+=(--disableccusage) + nohup "$DAEMON_SCRIPT" "${DAEMON_ARGS[@]}" > /dev/null 2>&1 & else - nohup "$DAEMON_SCRIPT" > /dev/null 2>&1 & + nohup "$DAEMON_SCRIPT" "${DAEMON_ARGS[@]}" > /dev/null 2>&1 & fi - + sleep 2 - + if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") if kill -0 "$PID" 2>/dev/null; then @@ -152,7 +192,7 @@ start_daemon() { return 0 fi fi - + print_error "Failed to start daemon" return 1 } @@ -162,18 +202,18 @@ stop_daemon() { print_warning "Daemon is not running (no PID file found)" return 1 fi - + PID=$(cat "$PID_FILE") - + if ! kill -0 "$PID" 2>/dev/null; then print_warning "Daemon is not running (process $PID not found)" rm -f "$PID_FILE" return 1 fi - + print_status "Stopping daemon with PID $PID..." kill "$PID" - + # Wait for graceful shutdown for i in {1..10}; do if ! kill -0 "$PID" 2>/dev/null; then @@ -183,7 +223,7 @@ stop_daemon() { fi sleep 1 done - + # Force kill if still running print_warning "Daemon did not stop gracefully, forcing..." kill -9 "$PID" 2>/dev/null @@ -196,15 +236,15 @@ get_daemon_timing_info() { current_epoch=$(date +%s) start_epoch="" stop_epoch="" - + if [ -f "$START_TIME_FILE" ]; then start_epoch=$(cat "$START_TIME_FILE") fi - + if [ -f "$STOP_TIME_FILE" ]; then stop_epoch=$(cat "$STOP_TIME_FILE") fi - + # Return values via global variables CURRENT_EPOCH="$current_epoch" START_EPOCH="$start_epoch" @@ -214,7 +254,7 @@ get_daemon_timing_info() { # Get daemon status information get_daemon_status() { get_daemon_timing_info - + # Determine current status if [ -n "$START_EPOCH" ] && [ "$CURRENT_EPOCH" -lt "$START_EPOCH" ]; then # Before start time @@ -252,17 +292,17 @@ get_daemon_status() { # Get next renewal estimate get_next_renewal_estimate() { get_daemon_timing_info - + NEXT_RENEWAL_TIME="" NEXT_RENEWAL_REMAINING="" - + # Only show if active or no scheduling if [ ! -f "$START_TIME_FILE" ] || [ "$CURRENT_EPOCH" -ge "$(cat "$START_TIME_FILE" 2>/dev/null || echo 0)" ]; then if [ -f "$HOME/.claude-last-activity" ]; then last_activity=$(cat "$HOME/.claude-last-activity") time_diff=$((CURRENT_EPOCH - last_activity)) remaining=$((18000 - time_diff)) - + if [ $remaining -gt 0 ]; then hours=$((remaining / 3600)) minutes=$(((remaining % 3600) / 60)) @@ -277,45 +317,45 @@ get_next_renewal_estimate() { # Generate day plan with estimated renewal times generate_day_plan() { get_daemon_timing_info - + # Clear the day plan array DAY_PLAN=() - + # Get current date for calculations current_date=$(date '+%Y-%m-%d') day_start_epoch=$(date -d "$current_date 00:00:00" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S" "$current_date 00:00:00" +%s 2>/dev/null) day_end_epoch=$((day_start_epoch + 86400)) - + # Determine the active window for today active_start=$day_start_epoch active_end=$day_end_epoch - + if [ -n "$START_EPOCH" ]; then # Use today's version of start time start_time_today=$(date -d "@$START_EPOCH" '+%H:%M:%S' 2>/dev/null || date -r "$START_EPOCH" '+%H:%M:%S') active_start=$(date -d "$current_date $start_time_today" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S" "$current_date $start_time_today" +%s 2>/dev/null) fi - + if [ -n "$STOP_EPOCH" ]; then # Use today's version of stop time stop_time_today=$(date -d "@$STOP_EPOCH" '+%H:%M:%S' 2>/dev/null || date -r "$STOP_EPOCH" '+%H:%M:%S') active_end=$(date -d "$current_date $stop_time_today" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M:%S" "$current_date $stop_time_today" +%s 2>/dev/null) fi - + # If we have last activity, calculate potential renewal times if [ -f "$HOME/.claude-last-activity" ]; then last_activity=$(cat "$HOME/.claude-last-activity") - + # Calculate the first potential renewal after last activity first_renewal=$((last_activity + 18000)) # 5 hours after last activity - + # Generate renewal times throughout the day current_renewal=$first_renewal while [ $current_renewal -lt $day_end_epoch ]; do # Check if this renewal time is within active hours if [ $current_renewal -ge $active_start ] && [ $current_renewal -le $active_end ]; then renewal_time_str=$(date -d "@$current_renewal" '+%H:%M' 2>/dev/null || date -r "$current_renewal" '+%H:%M') - + # Mark if this is the next upcoming renewal if [ $current_renewal -gt $CURRENT_EPOCH ]; then if [ ${#DAY_PLAN[@]} -eq 0 ]; then @@ -331,12 +371,12 @@ generate_day_plan() { DAY_PLAN+=("$renewal_time_str") fi fi - + # Next renewal is 5 hours later current_renewal=$((current_renewal + 18000)) done fi - + # If no renewals planned, show when monitoring is active if [ ${#DAY_PLAN[@]} -eq 0 ]; then if [ -n "$START_EPOCH" ] && [ -n "$STOP_EPOCH" ]; then @@ -356,34 +396,34 @@ create_progress_bar() { local current_time="$1" local total_time="$2" local remaining_time="$3" - + if [ $total_time -le 0 ]; then echo "No progress data available" return fi - + # Calculate percentage local elapsed_time=$((total_time - remaining_time)) local percentage=$((elapsed_time * 100 / total_time)) - + # Ensure percentage is within bounds if [ $percentage -lt 0 ]; then percentage=0 elif [ $percentage -gt 100 ]; then percentage=100 fi - + # Create the bar (40 characters wide) local bar_length=40 local filled_length=$((percentage * bar_length / 100)) local empty_length=$((bar_length - filled_length)) - + # Color codes local green='\033[0;32m' local yellow='\033[1;33m' local red='\033[0;31m' local nc='\033[0m' - + # Choose color based on remaining time local color="$green" if [ $remaining_time -lt 1800 ]; then # Less than 30 minutes @@ -391,25 +431,25 @@ create_progress_bar() { elif [ $remaining_time -lt 3600 ]; then # Less than 1 hour color="$yellow" fi - + # Build the progress bar local filled_bar="" local empty_bar="" - + # Create filled portion for i in $(seq 1 $filled_length); do filled_bar="${filled_bar}█" done - - # Create empty portion + + # Create empty portion for i in $(seq 1 $empty_length); do empty_bar="${empty_bar}░" done - + # Format remaining time local hours=$((remaining_time / 3600)) local minutes=$(((remaining_time % 3600) / 60)) - + # Display the progress bar echo -e " ${color}${filled_bar}${nc}${empty_bar} ${percentage}% (${hours}h ${minutes}m remaining)" } @@ -422,7 +462,7 @@ dash_daemon() { echo "Start the daemon with: $0 start" return 1 fi - + PID=$(cat "$PID_FILE") if ! kill -0 "$PID" 2>/dev/null; then print_error "Daemon is not running (process $PID not found)" @@ -430,14 +470,14 @@ dash_daemon() { echo "Start the daemon with: $0 start" return 1 fi - + # Trap Ctrl+C to exit gracefully trap 'echo ""; echo "Dashboard stopped."; exit 0' INT - + echo "Claude Auto-Renewal Dashboard (Press Ctrl+C to exit)" echo "Updating every minute..." echo "" - + while true; do # Clear screen and show header clear @@ -446,10 +486,10 @@ dash_daemon() { echo "║ $(date '+%A, %B %d, %Y - %H:%M:%S') ║" echo "╚══════════════════════════════════════════════════════════════════════════════╝" echo "" - + # Get current daemon status get_daemon_status - + echo "🔧 DAEMON STATUS:" echo " PID: $PID" echo " Status: $DAEMON_STATUS_TEXT" @@ -457,7 +497,7 @@ dash_daemon() { echo -e "$DAEMON_STATUS_DETAIL" | sed 's/^/ /' fi echo "" - + # Show progress bar for next renewal get_next_renewal_estimate if [ -n "$NEXT_RENEWAL_REMAINING" ]; then @@ -468,7 +508,7 @@ dash_daemon() { current_time=$(date +%s) time_diff=$((current_time - last_activity)) remaining=$((18000 - time_diff)) - + if [ $remaining -gt 0 ]; then create_progress_bar "$current_time" 18000 "$remaining" echo " Next renewal at: $NEXT_RENEWAL_TIME" @@ -481,7 +521,7 @@ dash_daemon() { echo " No active renewal tracking" fi echo "" - + # Show day plan generate_day_plan echo "📅 TODAY'S RENEWAL PLAN:" @@ -493,7 +533,7 @@ dash_daemon() { echo " No renewal plan available" fi echo "" - + # Show recent activity if [ -f "$LOG_FILE" ]; then echo "📝 RECENT ACTIVITY:" @@ -503,9 +543,9 @@ dash_daemon() { echo " No log file found" fi echo "" - + echo "Last updated: $(date '+%H:%M:%S') | Press Ctrl+C to exit" - + # Wait 60 seconds before next update sleep 60 done @@ -516,12 +556,12 @@ status_daemon() { print_status "Daemon is not running" return 1 fi - + PID=$(cat "$PID_FILE") - + if kill -0 "$PID" 2>/dev/null; then print_status "Daemon is running with PID $PID" - + get_daemon_status print_status "Status: $DAEMON_STATUS_TEXT" if [ -n "$DAEMON_STATUS_DETAIL" ]; then @@ -529,21 +569,21 @@ status_daemon() { print_status "$line" done fi - + # Show recent activity if [ -f "$LOG_FILE" ]; then echo "" print_status "Recent activity:" tail -5 "$LOG_FILE" | sed 's/^/ /' fi - + # Show next renewal estimate get_next_renewal_estimate if [ -n "$NEXT_RENEWAL_REMAINING" ]; then echo "" print_status "Estimated time until next renewal: $NEXT_RENEWAL_REMAINING" fi - + return 0 else print_warning "Daemon is not running (process $PID not found)" @@ -564,7 +604,7 @@ show_logs() { print_error "No log file found" return 1 fi - + if [ "$1" = "-f" ]; then tail -f "$LOG_FILE" else @@ -604,10 +644,13 @@ case "$1" in echo " start --at TIME - Start daemon but begin monitoring at specified time" echo " start --at TIME --stop END - Start monitoring at TIME, stop at END" echo " start --disableccusage - Start daemon without ccusage (clock-based only)" + echo " start --model NAME - Use a specific Claude model for renewals" + echo " start --effort LEVEL - Use a specific Claude effort level" echo " start --message \"text\" - Use custom message for renewal instead of random greetings" echo " Examples: --at '09:00' --stop '17:00'" echo " --at '2025-01-28 09:00' --stop '2025-01-28 17:00'" echo " --at '09:00' --stop '17:00' --disableccusage" + echo " --model 'haiku' --effort 'low'" echo " --message 'continue working on the React feature'" echo " stop - Stop the daemon" echo " restart - Restart the daemon"