diff --git a/openclaw-setup-max/README.md b/openclaw-setup-max/README.md index a8720fe..80daed0 100644 --- a/openclaw-setup-max/README.md +++ b/openclaw-setup-max/README.md @@ -40,6 +40,8 @@ Profiles are defined in `config/model-profiles.config.json`. - `config/model-profiles.config.json`: model profile definitions (`paid`, `free`) - `config/model-schedule.config.json`: schedule settings (`dayProfile`, `nightProfile`, hours) - `config/model-budget-guard.config.json`: high-cost warning and auto-revert settings +- `config/models.env`: single-source model + schedule variables +- `scripts/sync_models_from_env.sh`: sync JSON configs from `config/models.env` - `scripts/model_profile_switch.sh`: manual live switch command - `scripts/model_hygiene_workflow.sh`: apply profile + prune legacy model/provider/auth config - `scripts/model_schedule_guard.sh`: scheduled switch worker @@ -84,13 +86,16 @@ PULL_LOCAL_MODELS=false bash ./setup/setup_openclaw_ollama.sh ## 4) Quick Start (Most Common) -Run the hygiene workflow (recommended after any model changes): +Use env-driven sync + hygiene workflow (recommended after any model changes): ```bash cd /Volumes/Data/openclaw-setups/openclaw-setup-max +bash ./scripts/sync_models_from_env.sh bash ./scripts/model_hygiene_workflow.sh paid ``` +Edit `config/models.env` whenever you want to change paid/free models or schedule windows. + Switch to free mode now: ```bash diff --git a/openclaw-setup-max/config/model-profiles.config.json b/openclaw-setup-max/config/model-profiles.config.json index 7fd0a3f..f17d57f 100644 --- a/openclaw-setup-max/config/model-profiles.config.json +++ b/openclaw-setup-max/config/model-profiles.config.json @@ -2,7 +2,7 @@ "profiles": { "paid": { "description": "High-quality paid model for deep work", - "primary": "openrouter/qwen/qwen3-coder:free", + "primary": "openrouter/moonshotai/kimi-k2.5", "fallbacks": [ "openrouter/qwen/qwen3-coder-next", "openrouter/qwen/qwen3-14b" diff --git a/openclaw-setup-max/config/models.env b/openclaw-setup-max/config/models.env new file mode 100644 index 0000000..a840dfd --- /dev/null +++ b/openclaw-setup-max/config/models.env @@ -0,0 +1,10 @@ +# Model profile settings (single source of truth) +PAID_PRIMARY=openrouter/moonshotai/kimi-k2.5 +PAID_FALLBACKS=openrouter/qwen/qwen3-coder-next,openrouter/qwen/qwen3-14b + +FREE_PRIMARY=openrouter/qwen/qwen3-coder:free +FREE_FALLBACKS=openrouter/qwen/qwen3-14b + +# Format: dayProfile,nightProfile,dayStartHour,nightStartHour +SCHEDULE=paid,free,7,22 +SCHEDULE_ENABLED=true diff --git a/openclaw-setup-max/scripts/sync_models_from_env.sh b/openclaw-setup-max/scripts/sync_models_from_env.sh new file mode 100755 index 0000000..af059e5 --- /dev/null +++ b/openclaw-setup-max/scripts/sync_models_from_env.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +MODELS_ENV_FILE="${MODELS_ENV_FILE:-$ROOT_DIR/config/models.env}" +PROFILES_CONFIG="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}" +SCHEDULE_CONFIG="${MODEL_SCHEDULE_CONFIG:-$ROOT_DIR/config/model-schedule.config.json}" +BUDGET_CONFIG="${MODEL_GUARD_CONFIG:-$ROOT_DIR/config/model-budget-guard.config.json}" + +if ! command -v jq >/dev/null 2>&1; then + echo "[model-sync] jq is required" >&2 + exit 1 +fi + +usage() { + cat <<'USAGE' +Usage: + bash ./scripts/sync_models_from_env.sh + +Env source: + config/models.env (override with MODELS_ENV_FILE) + +Required variables: + PAID_PRIMARY + PAID_FALLBACKS + FREE_PRIMARY + FREE_FALLBACKS + SCHEDULE # dayProfile,nightProfile,dayStartHour,nightStartHour + +Optional variables: + SCHEDULE_ENABLED # true/false (default: true) +USAGE +} + +trim() { + local s="$1" + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" + printf '%s' "$s" +} + +csv_to_json_array() { + local csv="$1" + jq -cn --arg csv "$csv" ' + $csv + | split(",") + | map(gsub("^\\s+|\\s+$"; "")) + | map(select(length > 0)) + ' +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +if [[ ! -f "$MODELS_ENV_FILE" ]]; then + echo "[model-sync] missing env file: $MODELS_ENV_FILE" >&2 + exit 1 +fi +if [[ ! -f "$PROFILES_CONFIG" || ! -f "$SCHEDULE_CONFIG" || ! -f "$BUDGET_CONFIG" ]]; then + echo "[model-sync] required config files are missing" >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1090 +source "$MODELS_ENV_FILE" +set +a + +required=(PAID_PRIMARY PAID_FALLBACKS FREE_PRIMARY FREE_FALLBACKS SCHEDULE) +for key in "${required[@]}"; do + if [[ -z "${!key:-}" ]]; then + echo "[model-sync] missing required variable: $key" >&2 + exit 1 + fi +done + +paid_fallbacks_json="$(csv_to_json_array "$PAID_FALLBACKS")" +free_fallbacks_json="$(csv_to_json_array "$FREE_FALLBACKS")" + +if [[ "$(jq -r 'length' <<<"$paid_fallbacks_json")" == "0" ]]; then + echo "[model-sync] PAID_FALLBACKS cannot be empty" >&2 + exit 1 +fi +if [[ "$(jq -r 'length' <<<"$free_fallbacks_json")" == "0" ]]; then + echo "[model-sync] FREE_FALLBACKS cannot be empty" >&2 + exit 1 +fi + +IFS=',' read -r day_profile_raw night_profile_raw day_start_raw night_start_raw <<<"$SCHEDULE" +day_profile="$(trim "${day_profile_raw:-}")" +night_profile="$(trim "${night_profile_raw:-}")" +day_start="$(trim "${day_start_raw:-}")" +night_start="$(trim "${night_start_raw:-}")" + +if [[ -z "$day_profile" || -z "$night_profile" || -z "$day_start" || -z "$night_start" ]]; then + echo "[model-sync] SCHEDULE must be: dayProfile,nightProfile,dayStartHour,nightStartHour" >&2 + exit 1 +fi +if ! [[ "$day_start" =~ ^[0-9]+$ && "$night_start" =~ ^[0-9]+$ ]]; then + echo "[model-sync] schedule hours must be integers" >&2 + exit 1 +fi +if (( day_start < 0 || day_start > 23 || night_start < 0 || night_start > 23 )); then + echo "[model-sync] schedule hours must be in range 0..23" >&2 + exit 1 +fi + +schedule_enabled="$(trim "${SCHEDULE_ENABLED:-true}")" +if [[ "$schedule_enabled" != "true" && "$schedule_enabled" != "false" ]]; then + echo "[model-sync] SCHEDULE_ENABLED must be true or false" >&2 + exit 1 +fi + +tmp_profiles="$(mktemp)" +jq \ + --arg paid_primary "$PAID_PRIMARY" \ + --arg free_primary "$FREE_PRIMARY" \ + --argjson paid_fallbacks "$paid_fallbacks_json" \ + --argjson free_fallbacks "$free_fallbacks_json" \ + ' + .profiles.paid.primary = $paid_primary + | .profiles.paid.fallbacks = $paid_fallbacks + | .profiles.free.primary = $free_primary + | .profiles.free.fallbacks = $free_fallbacks + ' "$PROFILES_CONFIG" > "$tmp_profiles" +mv "$tmp_profiles" "$PROFILES_CONFIG" + +tmp_schedule="$(mktemp)" +jq \ + --arg day_profile "$day_profile" \ + --arg night_profile "$night_profile" \ + --argjson day_start "$day_start" \ + --argjson night_start "$night_start" \ + --arg enabled "$schedule_enabled" \ + ' + .enabled = ($enabled == "true") + | .dayProfile = $day_profile + | .nightProfile = $night_profile + | .dayStartHour = $day_start + | .nightStartHour = $night_start + ' "$SCHEDULE_CONFIG" > "$tmp_schedule" +mv "$tmp_schedule" "$SCHEDULE_CONFIG" + +tmp_budget="$(mktemp)" +jq \ + --arg low_model "$FREE_PRIMARY" \ + '.lowModel = $low_model' \ + "$BUDGET_CONFIG" > "$tmp_budget" +mv "$tmp_budget" "$BUDGET_CONFIG" + +echo "[model-sync] updated:" +echo " - $PROFILES_CONFIG" +echo " - $SCHEDULE_CONFIG" +echo " - $BUDGET_CONFIG" +echo "" +echo "[model-sync] paid primary: $(jq -r '.profiles.paid.primary' "$PROFILES_CONFIG")" +echo "[model-sync] free primary: $(jq -r '.profiles.free.primary' "$PROFILES_CONFIG")" +echo "[model-sync] schedule: $(jq -r '.enabled, .dayProfile, .nightProfile, .dayStartHour, .nightStartHour' "$SCHEDULE_CONFIG" | paste -sd ',' -)" +echo "[model-sync] budget lowModel: $(jq -r '.lowModel' "$BUDGET_CONFIG")"