#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" CONFIG_PATH="${MODEL_SCHEDULE_CONFIG:-$ROOT_DIR/config/model-schedule.config.json}" if ! command -v jq >/dev/null 2>&1; then echo "[model-schedule] jq is required" >&2 exit 1 fi if ! command -v openclaw >/dev/null 2>&1; then echo "[model-schedule] openclaw CLI is required" >&2 exit 1 fi expand_tilde() { local p="$1" if [[ "$p" == "~" ]]; then echo "$HOME" elif [[ "$p" == "~/"* ]]; then echo "$HOME/${p#~/}" else echo "$p" fi } if [[ ! -f "$CONFIG_PATH" ]]; then echo "[model-schedule] missing config: $CONFIG_PATH" >&2 exit 1 fi enabled="$(jq -r '.enabled // false' "$CONFIG_PATH")" if [[ "$enabled" != "true" ]]; then exit 0 fi day_profile="$(jq -r '.dayProfile // "paid"' "$CONFIG_PATH")" night_profile="$(jq -r '.nightProfile // "free"' "$CONFIG_PATH")" day_start_hour="$(jq -r '.dayStartHour // 8' "$CONFIG_PATH")" night_start_hour="$(jq -r '.nightStartHour // 18' "$CONFIG_PATH")" session_key="$(jq -r '.sessionKey // "agent:main:main"' "$CONFIG_PATH")" switch_script_raw="$(jq -r '.switchScript // "./scripts/model_profile_switch.sh"' "$CONFIG_PATH")" state_file_raw="$(jq -r '.stateFile // "~/.openclaw/model-schedule-state.json"' "$CONFIG_PATH")" state_file="$(expand_tilde "$state_file_raw")" profiles_config="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}" if [[ "$switch_script_raw" = /* ]]; then switch_script="$switch_script_raw" else switch_script="$ROOT_DIR/${switch_script_raw#./}" fi if [[ ! -x "$switch_script" ]]; then echo "[model-schedule] switch script is not executable: $switch_script" >&2 exit 1 fi if ! [[ "$day_start_hour" =~ ^[0-9]+$ && "$night_start_hour" =~ ^[0-9]+$ ]]; then echo "[model-schedule] dayStartHour/nightStartHour must be integers (0-23)" >&2 exit 1 fi if (( day_start_hour < 0 || day_start_hour > 23 || night_start_hour < 0 || night_start_hour > 23 )); then echo "[model-schedule] dayStartHour/nightStartHour must be in 0..23" >&2 exit 1 fi if (( day_start_hour == night_start_hour )); then echo "[model-schedule] dayStartHour and nightStartHour cannot be equal" >&2 exit 1 fi current_hour="$(date +%H)" current_hour=$((10#$current_hour)) is_night="false" if (( night_start_hour > day_start_hour )); then if (( current_hour >= night_start_hour || current_hour < day_start_hour )); then is_night="true" fi else if (( current_hour >= night_start_hour && current_hour < day_start_hour )); then is_night="true" fi fi desired_profile="$day_profile" if [[ "$is_night" == "true" ]]; then desired_profile="$night_profile" fi mkdir -p "$(dirname "$state_file")" if [[ ! -f "$state_file" ]]; then printf '{}\n' > "$state_file" fi last_profile="$(jq -r '.lastAppliedProfile // empty' "$state_file")" desired_primary="" if [[ -f "$profiles_config" ]]; then desired_primary="$(jq -r --arg p "$desired_profile" '.profiles[$p].primary // empty' "$profiles_config")" fi current_primary="$(openclaw models status --json 2>/dev/null | jq -r '.defaultModel // empty')" if [[ "$last_profile" == "$desired_profile" ]]; then if [[ -n "$desired_primary" && "$current_primary" == "$desired_primary" ]]; then exit 0 fi fi echo "[model-schedule] hour=$current_hour desired_profile=$desired_profile last_profile=${last_profile:-none} current_primary=${current_primary:-unknown}" MODEL_SWITCH_SESSION_KEY="$session_key" bash "$switch_script" "$desired_profile" --no-live --no-status tmp_file="$state_file.tmp" jq \ --arg profile "$desired_profile" \ --arg when "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ --arg session "$session_key" \ '.lastAppliedProfile=$profile | .lastAppliedAt=$when | .sessionKey=$session' \ "$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file"