Remove openclaw-setup-copilot files

This commit is contained in:
Matt Bruce 2026-02-20 12:38:05 -06:00
parent e0ef96e753
commit fb924a7fa6
32 changed files with 0 additions and 3269 deletions

View File

@ -1,125 +0,0 @@
# AGENTS.md - Work Machine Setup Template
This folder is now a setup template for another computer.
Important:
- Do not run install or config mutation commands on this current machine.
- Use these docs as the source of truth when setting up your work computer.
## Mode Toggle (Read First)
Current mode in this copy:
- `LOCAL-SAFETY MODE` is active (no install/mutation on this machine).
When you copy this folder to the target work machine:
1. Remove or rewrite the `LOCAL-SAFETY MODE` block below.
2. Uncomment the `TARGET-INSTALL MODE` block.
3. Follow `docs/operations/WORK_SETUP_CHECKLIST.md`.
### LOCAL-SAFETY MODE (ACTIVE NOW)
- Do not run install commands here.
- Do not run `scripts/install_copilot_guardrails.sh` here.
- Do not run `scripts/finalize_copilot_setup.sh` here.
- Do not run provider/model mutation commands here.
- Use this copy only for editing docs/scripts before transfer.
<!--
### TARGET-INSTALL MODE
- This folder is now running on the intended target work machine.
- You are allowed to run install and mutation commands in this workspace.
Required setup order:
1. `bash ./setup/setup_openclaw_copilot.sh`
2. `bash ./scripts/finalize_copilot_setup.sh`
3. `openclaw status --deep`
Required verification:
- `openclaw models status` shows `github-copilot/*` routing.
- LaunchAgents loaded:
- `ai.openclaw.model-budget-guard`
- `ai.openclaw.copilot-policy-guard`
- `ai.openclaw.copilot-auth-watchdog`
- `ai.openclaw.copilot-model-schedule-guard`
- Schedule behavior:
- `08:00-18:00` local -> `paid` profile
- `18:00-08:00` local -> `free` profile
-->
## Goal
Create a reliable OpenClaw setup that uses GitHub Copilot CLI Enterprise as the main provider, with a Senior iOS Engineer persona focused on architecture and refactoring.
## Setup Flow (target computer only)
1. Run `setup/setup_openclaw_copilot.sh` for Copilot-first baseline.
2. Run `scripts/finalize_copilot_setup.sh` to complete auth + profiles + guardrails in one command.
3. Verify gateway/model health.
## Copilot CLI Install and Auth
```bash
# preferred for latest models
brew install copilot-cli@prerelease
# or
npm install -g @github/copilot-cli
copilot auth login
copilot auth status
```
## OpenClaw Copilot Model Routing
### If your OpenClaw supports `config patch`
Use your allowlist patch workflow.
### If your OpenClaw does NOT support `config patch`
Use direct commands:
```bash
openclaw models set github-copilot/claude-sonnet-4.6
openclaw models fallbacks clear
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-1>
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-2>
openclaw config set --json providers.github-copilot.enabled true
openclaw config set --json providers.openai.enabled false
openclaw config set --json providers.anthropic.enabled false
openclaw config set --json providers.openrouter.enabled false
```
## Session Startup Routine
1. Read `docs/context/SOUL.md`
2. Read `docs/context/USER.md`
3. Read `memory/YYYY-MM-DD.md` (today and yesterday)
4. In direct owner chat, also read `docs/context/MEMORY.md`
5. Run health check:
```bash
openclaw status --deep
openclaw models status
```
Model guard should be active on target machine:
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
```
## Persona Requirement
Always operate as a Senior iOS Engineer:
- Swift/SwiftUI architecture first
- Strong refactoring discipline
- Clear boundaries and testability
- Concise, practical guidance
## Safety
- Never print full tokens.
- Never post externally without explicit instruction.
- Prefer non-destructive actions.

View File

@ -1,41 +0,0 @@
# PRD: OpenClaw Copilot Workstation Setup
## Purpose
Provide a repeatable, low-risk setup for a Copilot-only OpenClaw workstation with cost controls and schedule-based model routing.
## Goals
- Enforce `github-copilot/*` as the only provider/model family.
- Use paid profile during work hours (`08:00-18:00` local).
- Use free/low-cost profile off-hours (`18:00-08:00` local).
- Auto-heal routing/policy drift via launchd guardrails.
- Keep installation easy for AI-assisted and human-assisted setup.
## Scope
- Config-driven profile routing (`config/model-profiles.config.json`).
- Schedule guard (`scripts/model_schedule_guard.sh` + launchd).
- Budget guard, policy guard, auth watchdog.
- Staged launchd runtime under:
- `~/Library/Application Support/openclaw-copilot-guard`
## Non-Goals
- Supporting non-Copilot providers in normal operation.
- Cross-platform install workflows (target is macOS).
- Billing automation outside model/provider selection.
## Required Outcomes
- `scripts/install_copilot_guardrails.sh` installs all 4 guards:
- `ai.openclaw.model-budget-guard`
- `ai.openclaw.copilot-policy-guard`
- `ai.openclaw.copilot-auth-watchdog`
- `ai.openclaw.copilot-model-schedule-guard`
- Off-hours traffic defaults to free/low-cost profile.
- Work-hours defaults return to paid profile.
- Docs clearly describe setup, verification, troubleshooting.
## Success Criteria
- Fresh machine setup succeeds using:
- `setup/setup_openclaw_copilot.sh`
- `copilot auth login`
- `bash ./scripts/install_copilot_guardrails.sh`
- `openclaw models status` reflects expected profile by local time window.
- Launchd checks/logs show healthy runs and no repeated hard failures.

View File

@ -1,340 +0,0 @@
# OpenClaw Work Setup Template
Template workspace for setting up OpenClaw on a work computer with GitHub Copilot CLI Enterprise as the primary model provider, plus an iOS-focused assistant persona.
## Purpose
Use this folder as a repeatable setup baseline on another machine.
- Primary provider: GitHub Copilot Enterprise
- Persona: Senior iOS Engineer (architecture + refactoring focused)
## Important
This repository is intended as a guide/template.
Do not run mutation commands on an already-stable machine unless intended.
## Workspace Layout
Keep only operator docs at root:
- `AGENTS.md`
- `README.md`
- `PRD.md`
Everything else is organized by purpose:
- `setup/`: one-time bootstrap installers
- `scripts/`: guardrails, switching, and launchd installers
- `config/`: editable policy/profile/schedule/auth JSON
- `docs/context/`: runtime persona + user/context files
- `docs/operations/`: handoff/checklist/troubleshooting docs
- `memory/`: session memory files
## Workspace Files
- `AGENTS.md`: operating rules and setup sequence
- `docs/context/BOOT.md`: startup checks
- `docs/context/SOUL.md`: persona behavior
- `docs/context/USER.md`: user context
- `docs/context/TOOLS.md`: command reference
- `docs/operations/troubleshooting.md`: failure recovery runbook
- `setup/setup_openclaw_copilot.sh`: primary Copilot Enterprise setup
- `scripts/finalize_copilot_setup.sh`: one-command auth + model/profile + guardrail finalize
- `config/copilot-policy-guard.config.json`: Copilot routing/provider policy guard config
- `config/copilot-auth-watchdog.config.json`: Copilot auth watchdog config
- `config/model-profiles.config.json`: paid/free profile routing definitions
- `config/model-schedule.config.json`: work-hours/off-hours schedule config
- `scripts/install_copilot_guardrails.sh`: installs all launchd guardrails
## Target Machine Prerequisites
- macOS with Homebrew
- Node.js + npm
- OpenClaw installed
- GitHub Enterprise account with Copilot CLI entitlement
## Telegram Note (If Used)
- You do **not** need a new Telegram user account or phone number.
- One personal Telegram account is enough.
- Create a bot with `@BotFather`; bots are separate from user accounts.
- You chat with the bot from your existing Telegram account.
## Setup (Target Computer)
AI handoff:
- Use `docs/operations/AI_SETUP_HANDOFF.md` when delegating setup to another AI assistant.
- It includes a strict prompt, command order, and verification checks.
Quick path (copy/paste):
```bash
bash ./setup/setup_openclaw_copilot.sh
bash ./scripts/finalize_copilot_setup.sh
openclaw status --deep
```
Optional custom install locations:
```bash
# Move OpenClaw runtime data (~/.openclaw) to another path
OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot bash ./setup/setup_openclaw_copilot.sh
# Install global npm CLIs into a custom prefix/bin
NPM_GLOBAL_PREFIX="$HOME/.npm-global" bash ./setup/setup_openclaw_copilot.sh
# Use both
OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot \
NPM_GLOBAL_PREFIX="$HOME/.npm-global" \
bash ./setup/setup_openclaw_copilot.sh
```
Order and dependency (important):
1. `setup/setup_openclaw_copilot.sh` installs tooling only (`openclaw`, `copilot`, Node).
2. `scripts/finalize_copilot_setup.sh` handles login, model discovery, profile selection, guardrails, hooks, and gateway restart.
3. If browser auth cannot complete inside finalize flow, run `copilot auth login`, then rerun finalize.
Why:
- OpenClaw installation does not require Copilot login.
- `github-copilot/*` model selection and policy checks do require Copilot auth.
- Finalize script prevents users from forgetting model/profile/guardrail wiring after login.
1. Primary Copilot setup:
```bash
bash ./setup/setup_openclaw_copilot.sh
```
If using a custom location:
```bash
OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot bash ./setup/setup_openclaw_copilot.sh
```
2. Finalize in one command (recommended):
```bash
bash ./scripts/finalize_copilot_setup.sh
```
What finalize does:
- Opens `copilot auth login` if needed
- Refreshes model catalog
- Prompts you to choose the paid profile model from non-free candidates
- Auto-picks the free profile model/fallbacks from low-cost candidates
- Installs all guardrails
- Enables recommended hooks
- Restarts gateway
3. Verify:
```bash
copilot auth status
openclaw models status
openclaw status --deep
```
If login is missing/expired, finalize will prompt for login and retry setup.
For unattended/non-interactive automation:
```bash
PROMPT_FOR_PAID_MODEL=false bash ./scripts/finalize_copilot_setup.sh
```
### How To Choose Copilot Models (and Why It Matters)
Choosing the right primary and fallback models is important because it controls:
- Response speed in chat/Telegram
- Quality of coding/refactoring output
- Enterprise quota burn and cost exposure
- Reliability when one model is rate-limited or unavailable
Fallback policy for this template:
- Fallbacks must be free-tier or lowest-cost models only.
- Do not use premium fallbacks (for example Opus-class) as defaults.
- In strict Copilot-only mode, "free" means no extra external provider billing and lowest-burn models in your enterprise seat.
Recommended strategy:
1. Set a fast, general coding model as `primary` for daily work.
2. Add 1-2 low-cost fallbacks only.
3. Keep names exactly as shown in `openclaw models list` to avoid unknown-model failures.
Selection guide:
- Fast default: choose the quickest Sonnet/Codex variant your seat exposes
- Prefer fast/cheap variants over premium models for fallback
- Avoid premium fallbacks as defaults to prevent silent quota drain
4. Set Copilot model routing:
```bash
openclaw models set github-copilot/claude-sonnet-4.6
openclaw models fallbacks clear
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-1>
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-2>
```
After setting this, always verify:
```bash
openclaw models status
```
You should see a Copilot model as default plus your fallback chain.
5. Optional strict provider lock (if policy requires strict allowlist):
```bash
openclaw config set --json providers.github-copilot.enabled true
openclaw config set --json providers.openai.enabled false
openclaw config set --json providers.anthropic.enabled false
openclaw config set --json providers.openrouter.enabled false
```
6. Restart and verify:
```bash
openclaw gateway restart
openclaw status --deep
openclaw models status
```
7. Configure + install Copilot guardrails (recommended):
```bash
bash ./scripts/install_copilot_guardrails.sh
```
Guard config files:
- `config/model-budget-guard.config.json`
- `config/copilot-policy-guard.config.json`
- `config/copilot-auth-watchdog.config.json`
- `config/model-profiles.config.json`
- `config/model-schedule.config.json`
Runtime staging note (important):
- Installer stages active guard scripts/config into:
- `~/Library/Application Support/openclaw-copilot-guard`
- Launchd runs guards from that staged folder, not directly from repository path.
- After editing guard scripts/config in this repo, re-run:
- `bash ./scripts/install_copilot_guardrails.sh`
to sync staged runtime files.
What it does:
- Model budget guard: warns on high-cost model usage and auto-reverts to low-cost model
- Copilot policy guard: enforces Copilot-only provider/model policy and fixes drift
- Copilot auth watchdog: alerts when Copilot auth expires or becomes unhealthy
- Model schedule guard: uses paid profile during work hours and free profile off-hours
Why this is important:
- Prevents someone from staying on high-tier models all day
- Reduces accidental enterprise quota burn
- Keeps day-to-day latency faster with a low-cost default
Default schedule behavior:
- Work hours (`08:00` to `18:00` local time): `paid` profile
- Off-hours (`18:00` to `08:00` local time): `free` profile
Schedule wiring (important):
- `config/model-schedule.config.json` controls time windows and which script applies profiles.
- `switchScript` should be `./scripts/model_profile_switch.sh`.
- `config/model-profiles.config.json` defines what `paid` and `free` actually mean (primary + fallbacks).
Expected schedule config shape:
```json
{
"enabled": true,
"dayProfile": "paid",
"nightProfile": "free",
"dayStartHour": 8,
"nightStartHour": 18,
"sessionKey": "agent:main:main",
"switchScript": "./scripts/model_profile_switch.sh",
"stateFile": "~/.openclaw/model-schedule-state.json"
}
```
Important behavior difference vs Max:
- Copilot `config/model-profiles.config.json` starts with empty model IDs in this template copy.
- `bash ./scripts/finalize_copilot_setup.sh` (or `bash ./scripts/install_copilot_guardrails.sh`) runs `configure_copilot_guardrails_defaults.sh`, detects available `github-copilot/*` models, and writes concrete `paid`/`free` models into `config/model-profiles.config.json`.
- Until that step runs on the target machine (after `copilot auth login`), schedule/profile switching has no concrete model IDs to apply.
Quick verify commands:
```bash
cat config/model-schedule.config.json
cat config/model-profiles.config.json
```
After installing guardrails, verify staged runtime files used by launchd:
```bash
cat ~/Library/Application\ Support/openclaw-copilot-guard/model-schedule.config.json
cat ~/Library/Application\ Support/openclaw-copilot-guard/model-profiles.config.json
```
Manual profile switch:
```bash
bash ./scripts/model_profile_switch.sh paid
bash ./scripts/model_profile_switch.sh free
bash ./scripts/model_profile_switch.sh status
```
If you need a quick switch without sending a live `/model` message:
```bash
bash ./scripts/model_profile_switch.sh free --no-live
```
8. Enable recommended hooks:
```bash
openclaw hooks enable boot-md
openclaw hooks enable command-logger
openclaw hooks enable session-memory
```
9. Verify hooks:
```bash
openclaw hooks list
openclaw hooks list --eligible
```
## Daily Checks
```bash
openclaw status --deep
openclaw models status
copilot auth status
```
If responses feel slow or weak, re-check model routing first before debugging gateway/network.
Model guard health:
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-policy-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-auth-watchdog
launchctl print gui/$(id -u)/ai.openclaw.copilot-model-schedule-guard
tail -n 30 /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
tail -n 30 /tmp/openclaw-copilot-policy-guard.log /tmp/openclaw-copilot-policy-guard.err.log
tail -n 30 /tmp/openclaw-copilot-auth-watchdog.log /tmp/openclaw-copilot-auth-watchdog.err.log
tail -n 30 /tmp/openclaw-copilot-model-schedule-guard.log /tmp/openclaw-copilot-model-schedule-guard.err.log
```
## Troubleshooting
Start with `docs/operations/troubleshooting.md`.
## Security Notes
- Never print full API keys/tokens in logs or chat.
- Rotate any secret that may have been exposed.

View File

@ -1,7 +0,0 @@
{
"enabled": false,
"sessionKey": "agent:main:main",
"minAlertIntervalMinutes": 30,
"stateFile": "~/.openclaw/copilot-auth-watchdog-state.json",
"checkOpenClawModelStatus": true
}

View File

@ -1,18 +0,0 @@
{
"enabled": false,
"autoFix": true,
"sessionKey": "agent:main:main",
"minAlertIntervalMinutes": 20,
"stateFile": "~/.openclaw/copilot-policy-guard-state.json",
"requiredPrimaryPrefix": "github-copilot/",
"requiredFallbackPrefix": "github-copilot/",
"desiredPrimaryModel": "",
"enforceFallbackAllowlist": true,
"allowedFallbacks": [],
"providerPolicy": {
"github-copilot": true,
"openai": false,
"anthropic": false,
"openrouter": false
}
}

View File

@ -1,10 +0,0 @@
{
"enabled": false,
"sessionKey": "agent:main:main",
"lowModel": "",
"highModels": [],
"warnAfterMinutes": 2,
"revertAfterMinutes": 45,
"minWarnIntervalMinutes": 20,
"stateFile": "~/.openclaw/model-budget-guard-state.json"
}

View File

@ -1,26 +0,0 @@
{
"profiles": {
"paid": {
"description": "Work-hours higher-quality Copilot model",
"primary": "",
"fallbacks": [],
"providerPolicy": {
"github-copilot": true,
"openai": false,
"anthropic": false,
"openrouter": false
}
},
"free": {
"description": "Off-hours low-cost/free Copilot model stack",
"primary": "",
"fallbacks": [],
"providerPolicy": {
"github-copilot": true,
"openai": false,
"anthropic": false,
"openrouter": false
}
}
}
}

View File

@ -1,10 +0,0 @@
{
"enabled": true,
"dayProfile": "paid",
"nightProfile": "free",
"dayStartHour": 8,
"nightStartHour": 18,
"sessionKey": "agent:main:main",
"switchScript": "./scripts/model_profile_switch.sh",
"stateFile": "~/.openclaw/model-schedule-state.json"
}

View File

@ -1,72 +0,0 @@
# docs/context/BOOT.md - Target Computer Startup
This boot file is for the work machine, not this machine.
## 1) Verify services
```bash
openclaw status --deep
```
## 2) Verify Copilot auth
```bash
copilot auth status
```
If not authenticated:
```bash
copilot auth login
```
## 3) Verify model routing
```bash
openclaw models list
openclaw models status
```
Expected state:
- Active/default model is a `github-copilot/*` model.
- Fallbacks are `github-copilot/*` models only.
## 4) Gateway and channels
```bash
openclaw gateway restart
openclaw status --deep
```
Confirm channel health shows `OK`.
## 5) Model budget guardrail
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
```
If missing, install on target machine:
```bash
bash ./scripts/install_copilot_guardrails.sh
```
## 6) Copilot policy/auth guardrails
```bash
launchctl print gui/$(id -u)/ai.openclaw.copilot-policy-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-auth-watchdog
launchctl print gui/$(id -u)/ai.openclaw.copilot-model-schedule-guard
```
Schedule expectation:
- `08:00-18:00` local: `paid` profile
- `18:00-08:00` local: `free` profile
## 7) Hook status
```bash
openclaw hooks list
openclaw hooks list --eligible
```

View File

@ -1,10 +0,0 @@
# docs/context/HEARTBEAT.md
Run these checks only on the target work machine:
- `openclaw status --deep`
- `openclaw models status`
- `copilot auth status`
If all healthy and no action needed, reply:
`HEARTBEAT_OK`

View File

@ -1,9 +0,0 @@
# docs/context/IDENTITY.md - Runtime Identity
- Name: ClawCraft iOS
- Role: Senior iOS Engineer assistant
- Vibe: concise, pragmatic, architecture-first
- Focus: Swift, SwiftUI, refactoring, testing, clean architecture
- Signature: [ios-arch]
Primary mission is engineering execution quality, not generic assistant chatter.

View File

@ -1,22 +0,0 @@
# docs/context/MEMORY.md - Long-Term Context
## User Context
- Owner: Matt Bruce
- Prefers practical command-level guidance
- Wants local-first efficiency and provider flexibility
## Technical Direction
- Primary target: OpenClaw + GitHub Copilot CLI Enterprise on work machine
- Keep response quality tuned for iOS architecture and refactoring work
## Known Lessons
- Tool-capability mismatches can break Telegram/chat flows.
- Session resets (`/new`) reduce latency and token burn.
- Stable gateway launch settings and log paths matter on macOS.
## Scope Guardrail
This repository is now a setup guide template. Avoid changing runtime config on this current machine unless explicitly requested.

View File

@ -1,22 +0,0 @@
# docs/context/SOUL.md - Operating Profile
You are a Senior iOS Engineer assistant.
## Standards
- Be concrete and technically correct.
- Refactor toward clarity, maintainability, and testability.
- Prefer modern Swift and SwiftUI patterns.
- Keep explanations short unless deeper detail is requested.
## Decision Style
- Recommend practical paths.
- State tradeoffs clearly.
- Verify assumptions with evidence before advising.
## Boundaries
- Do not expose secrets.
- Ask before external actions.
- Avoid destructive operations without explicit approval.

View File

@ -1,121 +0,0 @@
# docs/context/TOOLS.md - Command Reference (Target Work Machine)
## GitHub Copilot CLI (Enterprise)
Primary setup script:
```bash
bash ./setup/setup_openclaw_copilot.sh
```
Custom locations (optional):
```bash
OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot bash ./setup/setup_openclaw_copilot.sh
NPM_GLOBAL_PREFIX="$HOME/.npm-global" bash ./setup/setup_openclaw_copilot.sh
```
Install:
```bash
brew install copilot-cli@prerelease
# or
npm install -g @github/copilot-cli
```
Authenticate:
```bash
copilot auth login
copilot auth status
```
## OpenClaw Model Discovery and Routing
```bash
openclaw models refresh
openclaw models list
openclaw models status
```
If `models refresh` is unavailable on your OpenClaw version, use `openclaw models list` and continue.
## Lock to Copilot-only providers
Preferred (if supported):
- `openclaw config patch '{...}'`
Fallback command style:
```bash
openclaw config set --json providers.github-copilot.enabled true
openclaw config set --json providers.openai.enabled false
openclaw config set --json providers.anthropic.enabled false
openclaw config set --json providers.openrouter.enabled false
```
## Health and Logs
```bash
openclaw status --deep
openclaw logs --follow
openclaw gateway restart
```
## Model Budget Guardrail (Target Machine)
Files:
- `config/model-budget-guard.config.json`
- `config/copilot-policy-guard.config.json`
- `config/copilot-auth-watchdog.config.json`
- `scripts/model_budget_guard.sh`
- `scripts/copilot_policy_guard.sh`
- `scripts/copilot_auth_watchdog.sh`
- `scripts/install_model_budget_guard_launchd.sh`
- `scripts/install_copilot_policy_guard_launchd.sh`
- `scripts/install_copilot_auth_watchdog_launchd.sh`
- `scripts/configure_copilot_guardrails_defaults.sh`
- `scripts/install_copilot_guardrails.sh`
Configure + install:
```bash
bash ./scripts/install_copilot_guardrails.sh
```
This command also runs:
```bash
bash ./scripts/configure_copilot_guardrails_defaults.sh
```
Verify:
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-policy-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-auth-watchdog
tail -n 30 /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
tail -n 30 /tmp/openclaw-copilot-policy-guard.log /tmp/openclaw-copilot-policy-guard.err.log
tail -n 30 /tmp/openclaw-copilot-auth-watchdog.log /tmp/openclaw-copilot-auth-watchdog.err.log
```
Behavior:
- warns when expensive model stays active
- auto-switches back to configured low model
- auto-fixes Copilot-only provider/model policy drift
- alerts on Copilot auth problems
## Hooks (Recommended)
```bash
openclaw hooks enable boot-md
openclaw hooks enable command-logger
openclaw hooks enable session-memory
openclaw hooks list
```
## Security
- Never paste full API keys or bot tokens into chat/logs.
- Rotate secrets if exposed.

View File

@ -1,38 +0,0 @@
# docs/context/USER.md - Human Context
- Name: `<user-name>`
- Preferred name: `<preferred-name>`
- Current objective: Copilot-only OpenClaw setup on work machine
## Priorities
- OpenClaw reliability
- Copilot Enterprise auth and model routing
- Senior iOS engineering persona quality
- Clear fallback strategy for provider/model issues
## Working Style
- Wants direct fixes and exact commands
- Comfortable in terminal workflows
- Values architecture/refactor quality over shortcuts
## Secrets Inputs (Placeholders Only)
Use this section for setup prompts. Do not commit real secrets.
- `COPILOT_AUTH`: browser login required (`copilot auth login`) — no static token stored here
- `TELEGRAM_BOT_TOKEN`: `<set-at-runtime-if-using-telegram>`
- `OPENCLAW_GATEWAY_TOKEN`: `<generated-by-openclaw-rotate-if-exposed>`
## Setup Prompt Checklist
When assisting a new user, prompt for:
1. Whether Telegram is needed (`yes/no`)
2. If yes, ask for `TELEGRAM_BOT_TOKEN` at runtime
3. Confirm Copilot browser login is completed
Telegram clarification:
- Existing personal Telegram account is sufficient.
- No new phone number/user account is needed to run a bot.

View File

@ -1,62 +0,0 @@
# docs/operations/AI_SETUP_HANDOFF.md
Give this file to an AI agent on the target machine.
## Copy/Paste Prompt For AI
```text
You are setting up this folder on this machine as a strict Copilot-only OpenClaw install.
Follow docs/operations/WORK_SETUP_CHECKLIST.md exactly.
Also follow the constraints below:
Constraints:
- Do NOT use Ollama.
- Do NOT enable non-Copilot providers.
- Use only github-copilot/* models.
- Fallbacks must be free/low-cost models only.
- Run commands in order and stop after each step to report status.
- If a step fails, provide exact fix and retry.
Run in this order:
1) bash ./setup/setup_openclaw_copilot.sh
2) bash ./scripts/finalize_copilot_setup.sh
- if prompted, pause and wait for user to complete browser login
- if prompted for paid model, show model list to user and ask user to choose
3) openclaw status --deep
After setup, verify and report:
- copilot auth status is healthy
- openclaw models status shows github-copilot/* primary
- fallbacks are low-cost only
- providers.github-copilot.enabled = true
- providers.openai.enabled = false
- providers.anthropic.enabled = false
- providers.openrouter.enabled = false
- launchd guards running:
- ai.openclaw.model-budget-guard
- ai.openclaw.copilot-policy-guard
- ai.openclaw.copilot-auth-watchdog
- ai.openclaw.copilot-model-schedule-guard
- schedule policy:
- paid profile during 08:00-18:00 local
- free profile during 18:00-08:00 local
- hooks enabled:
- boot-md
- command-logger
- session-memory
If any check fails, fix it and rerun verification.
```
## Manual User Actions Expected
- Complete enterprise login in browser when `finalize_copilot_setup.sh` opens `copilot auth login`.
- Approve enterprise/SSO/MFA prompts as required.
## One-Line Start (for human)
```bash
# open this file and copy the prompt to your AI agent
cat docs/operations/AI_SETUP_HANDOFF.md
```

View File

@ -1,244 +0,0 @@
# docs/operations/WORK_SETUP_CHECKLIST.md
Use this checklist on the **target work computer only**.
Do not run these mutation steps on your current stable machine.
## 0) Open terminal in this folder
```bash
cd /path/to/openclaw-setup
```
If delegating to another AI assistant, point it to:
- `docs/operations/AI_SETUP_HANDOFF.md`
## 1) Preflight
```bash
uname -a
sw_vers
which brew || echo "brew missing"
which node || echo "node missing"
which npm || echo "npm missing"
```
## 2) Primary Copilot setup
```bash
bash ./setup/setup_openclaw_copilot.sh
```
Optional custom locations:
```bash
OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot bash ./setup/setup_openclaw_copilot.sh
NPM_GLOBAL_PREFIX="$HOME/.npm-global" bash ./setup/setup_openclaw_copilot.sh
```
Verify:
```bash
which openclaw
openclaw --version
which copilot
copilot --version
```
## 3) Finalize setup in one command (recommended)
```bash
bash ./scripts/finalize_copilot_setup.sh
```
Expected:
- Authenticated with enterprise-linked account
- Copilot models discovered
- If prompted, paid profile model selected from non-free candidates
- Guardrails installed + running
- Hooks enabled (`boot-md`, `command-logger`, `session-memory`)
- Gateway restarted
If this step succeeds, you can skip to step 10.
## 4) Manual fallback path (advanced, only if step 3 fails)
If finalize fails due auth/browser flow, run:
```bash
copilot auth login
copilot auth status
```
Then continue below.
## 5) Start/verify OpenClaw gateway
```bash
openclaw gateway restart
openclaw status --deep
```
Expected: gateway reachable.
## 6) Discover available models
```bash
openclaw models refresh || true
openclaw models list
openclaw models status
```
Note: If `models refresh` is unsupported in your version, ignore and continue.
How to choose from the list:
- Pick a fast daily model as primary (usually Sonnet-Fast or Codex-Fast style naming).
- Pick only free-tier or lowest-cost fallbacks.
- Avoid premium fallbacks (Opus-class) in default routing.
- Keep model IDs exact from `openclaw models list`.
Why this matters:
- Better latency for normal work
- Better quality when complexity spikes
- Lower quota burn by not overusing heavyweight models
- Better uptime through fallback failover
## 7) Set Copilot primary + fallbacks
Replace models below with names from your `openclaw models list` output if needed.
```bash
openclaw models set github-copilot/claude-sonnet-4.6
openclaw models fallbacks clear
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-1>
openclaw models fallbacks add github-copilot/<free-or-low-cost-fallback-2>
```
Verify:
```bash
openclaw models status
```
Expected:
- Default model is your fast daily Copilot model
- Fallback chain includes only free-tier or low-cost models
- Only `github-copilot/*` appears if strict enterprise policy is required
## 8) Optional strict provider lock (Copilot-only)
Run if your enterprise policy requires only Copilot provider traffic:
```bash
openclaw config set --json providers.github-copilot.enabled true
openclaw config set --json providers.openai.enabled false
openclaw config set --json providers.anthropic.enabled false
openclaw config set --json providers.openrouter.enabled false
openclaw gateway restart
```
Verify:
```bash
openclaw models list
openclaw models status
```
## 9) Configure + install Copilot guardrails (recommended)
If step 3 already succeeded, this is already done.
This one command auto-detects your available Copilot models, asks for paid model selection (interactive), picks low-cost free defaults, applies policy, and installs launchd guards:
```bash
bash ./scripts/install_copilot_guardrails.sh
```
For unattended/non-interactive automation:
```bash
PROMPT_FOR_PAID_MODEL=false bash ./scripts/install_copilot_guardrails.sh
```
3. Verify:
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-policy-guard
launchctl print gui/$(id -u)/ai.openclaw.copilot-auth-watchdog
launchctl print gui/$(id -u)/ai.openclaw.copilot-model-schedule-guard
tail -n 30 /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
tail -n 30 /tmp/openclaw-copilot-policy-guard.log /tmp/openclaw-copilot-policy-guard.err.log
tail -n 30 /tmp/openclaw-copilot-auth-watchdog.log /tmp/openclaw-copilot-auth-watchdog.err.log
tail -n 30 /tmp/openclaw-copilot-model-schedule-guard.log /tmp/openclaw-copilot-model-schedule-guard.err.log
```
Why this matters:
- User gets prompted when high model remains active
- Session is auto-switched back to lower model after timeout
- Copilot-only policy is auto-corrected if drift occurs
- Expired Copilot auth is surfaced quickly
- Work-hours/off-hours profile schedule is auto-enforced
- Protects enterprise quota and keeps routine latency low
## 10) Enable recommended hooks
If step 3 already succeeded, hooks were already enabled.
```bash
openclaw hooks enable boot-md
openclaw hooks enable command-logger
openclaw hooks enable session-memory
openclaw hooks list
```
## 11) Persona/startup docs sanity
Confirm these files exist in workspace root:
```bash
ls -la AGENTS.md docs/context/BOOT.md docs/context/SOUL.md docs/context/IDENTITY.md docs/context/USER.md docs/context/TOOLS.md docs/operations/troubleshooting.md
```
## 12) Telegram/channel check (if used)
Telegram account rule:
- No new personal Telegram account is required.
- Use your existing Telegram account and create a bot via `@BotFather`.
- Bot token is the only setup secret needed for Telegram.
```bash
openclaw status --deep
```
Expected: channel `OK` and gateway reachable.
## 13) First chat checks
In your chat surface:
1. `/new`
2. Ask: `reply with your active model and one sentence`
Expected: response is fast and uses a `github-copilot/*` model.
## 14) Daily operations
```bash
openclaw status --deep
openclaw models status
copilot auth status
```
## 15) Fast failure recovery
```bash
openclaw gateway restart
openclaw logs --follow
openclaw models status
copilot auth status
```
If still blocked, use `docs/operations/troubleshooting.md`.

View File

@ -1,210 +0,0 @@
# Troubleshooting - OpenClaw + Copilot Enterprise (Target Machine)
This guide is for the work computer setup.
## Quick triage
```bash
openclaw status --deep
openclaw models status
copilot auth status
```
## 1) Copilot auth failure
Symptoms:
- `copilot auth status` not authenticated
- prompts fail with auth errors
Fix:
```bash
copilot auth login
copilot auth status
```
Make sure browser login uses the enterprise-linked GitHub account.
## 2) No Copilot model entitlement
Symptoms:
- auth is valid but model usage denied
Cause:
- org/enterprise policy does not permit Copilot CLI
Fix:
- ask enterprise admin to enable Copilot CLI entitlement for your seat
## 3) `Unknown model` in OpenClaw
Fix:
```bash
openclaw models refresh
openclaw models list
```
If `models refresh` is missing in your version, skip it and use `openclaw models list`.
## 4) OpenClaw `config patch` not available
Symptoms:
- guide references `openclaw config patch`, command not found
Fix:
- use `openclaw config set --json <path> <value>` commands instead
- use `openclaw models set` and `openclaw models fallbacks` commands for routing
## 5) Still seeing non-Copilot providers
Fix provider toggles:
```bash
openclaw config set --json providers.github-copilot.enabled true
openclaw config set --json providers.openai.enabled false
openclaw config set --json providers.anthropic.enabled false
openclaw config set --json providers.openrouter.enabled false
openclaw gateway restart
```
## 6) Telegram/channel slow responses
Fixes:
- start a fresh session with `/new`
- keep default model on a fast Copilot model
- verify gateway health and provider auth
```bash
openclaw status --deep
openclaw models status
```
Telegram account confusion:
- You do not need a second Telegram user account.
- Keep your current personal Telegram account.
- Create/connect only the bot token from `@BotFather`.
## 7) Cost and quota management
- Use fast/cheap Copilot model as default
- Use `/new` for unrelated tasks
- Use compaction if available in your OpenClaw build
## 8) Wrong model mix (common quality/latency issue)
Symptoms:
- Replies are slow even when gateway is healthy
- Refactor suggestions are weak/inconsistent
- Quota burns too fast for routine tasks
Cause:
- Heavy reasoning model set as default for all prompts
- No fallback diversity (all fast or all heavy)
Fix:
1. Set a fast default model for day-to-day requests.
2. Keep fallbacks free-tier or low-cost only.
3. Remove premium fallback models from default routing.
Then verify:
```bash
openclaw models list
openclaw models status
```
## 9) High-model guardrail not prompting or not switching back
Checks:
```bash
launchctl print gui/$(id -u)/ai.openclaw.model-budget-guard
tail -n 100 /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
cat config/model-budget-guard.config.json
cat ~/Library/Application\ Support/openclaw-copilot-guard/model-budget-guard.config.json
```
Common fixes:
- Wrong model IDs in `highModels`
- Use exact IDs from `openclaw models list`.
- `lowModel` not available
- Set to a model your seat can access.
- Guard not installed/loaded
- Re-run `bash ./scripts/install_copilot_guardrails.sh`.
- Repo config changed but staged runtime is stale
- Re-run `bash ./scripts/install_copilot_guardrails.sh` to re-stage current files.
- Session key mismatch
- Keep `sessionKey` as `agent:main:main` unless you intentionally use another session.
## 10) Copilot policy guard keeps fixing config unexpectedly
Cause:
- `config/copilot-policy-guard.config.json` has `autoFix=true` and policy values that differ from your manual changes.
Fix:
1. Edit `config/copilot-policy-guard.config.json`.
2. Set your intended `providerPolicy`, `desiredPrimaryModel`, and fallback rules.
3. If you want alert-only mode, set `autoFix` to `false`.
Verify logs:
```bash
tail -n 100 /tmp/openclaw-copilot-policy-guard.log /tmp/openclaw-copilot-policy-guard.err.log
cat ~/Library/Application\ Support/openclaw-copilot-guard/copilot-policy-guard.config.json
```
## 11) Manual model switch keeps changing back
Cause:
- Schedule guard is enabled and re-applies the expected profile by time window.
- `08:00-18:00` -> paid profile
- `18:00-08:00` -> free profile
Checks:
```bash
launchctl print gui/$(id -u)/ai.openclaw.copilot-model-schedule-guard
tail -n 100 /tmp/openclaw-copilot-model-schedule-guard.log /tmp/openclaw-copilot-model-schedule-guard.err.log
cat config/model-schedule.config.json
cat config/model-profiles.config.json
cat ~/Library/Application\ Support/openclaw-copilot-guard/model-schedule.config.json
cat ~/Library/Application\ Support/openclaw-copilot-guard/model-profiles.config.json
```
Fixes:
- If behavior is correct, do nothing (schedule is enforcing policy).
- To temporarily hold a manual switch, disable schedule in `config/model-schedule.config.json` and re-run:
- `bash ./scripts/install_copilot_guardrails.sh`
- For a quick manual switch without live push:
- `bash ./scripts/model_profile_switch.sh free --no-live`
- If `profiles.paid.primary` / `profiles.free.primary` are empty, run:
- `bash ./scripts/finalize_copilot_setup.sh`
This populates `config/model-profiles.config.json` and restages launchd runtime configs.
## 12) Copilot auth watchdog alerting repeatedly
Checks:
```bash
copilot auth status
openclaw models status --check
cat config/copilot-auth-watchdog.config.json
cat ~/Library/Application\ Support/openclaw-copilot-guard/copilot-auth-watchdog.config.json
```
Fixes:
- Re-authenticate with `copilot auth login`.
- Increase `minAlertIntervalMinutes` in `config/copilot-auth-watchdog.config.json`.
- If needed, temporarily disable watchdog by setting `enabled` to `false`.
## 13) Recommended hooks not active
Enable and verify:
```bash
openclaw hooks enable boot-md
openclaw hooks enable command-logger
openclaw hooks enable session-memory
openclaw hooks list
```

View File

@ -1,371 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
POLICY_CONFIG="$ROOT_DIR/config/copilot-policy-guard.config.json"
BUDGET_CONFIG="$ROOT_DIR/config/model-budget-guard.config.json"
AUTH_CONFIG="$ROOT_DIR/config/copilot-auth-watchdog.config.json"
MODEL_PROFILES_CONFIG="$ROOT_DIR/config/model-profiles.config.json"
MODEL_SCHEDULE_CONFIG="$ROOT_DIR/config/model-schedule.config.json"
if ! command -v openclaw >/dev/null 2>&1; then
echo "[configure-guardrails] openclaw CLI is required" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "[configure-guardrails] jq is required" >&2
exit 1
fi
if [[ ! -f "$MODEL_PROFILES_CONFIG" ]]; then
cat > "$MODEL_PROFILES_CONFIG" <<'JSON'
{
"profiles": {
"paid": {
"description": "Work-hours higher-quality Copilot model",
"primary": "",
"fallbacks": []
},
"free": {
"description": "Off-hours low-cost/free Copilot model stack",
"primary": "",
"fallbacks": []
}
}
}
JSON
fi
if [[ ! -f "$MODEL_SCHEDULE_CONFIG" ]]; then
cat > "$MODEL_SCHEDULE_CONFIG" <<'JSON'
{
"enabled": true,
"dayProfile": "paid",
"nightProfile": "free",
"dayStartHour": 8,
"nightStartHour": 18,
"sessionKey": "agent:main:main",
"switchScript": "./scripts/model_profile_switch.sh",
"stateFile": "~/.openclaw/model-schedule-state.json"
}
JSON
fi
is_cheap_model() {
local m
m="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
[[ "$m" =~ (haiku|mini|flash|fast|nano|small|lite|economy) ]]
}
prompt_for_paid_model() {
local default_model="$1"
shift
local -a candidates=("$@")
if [[ ${#candidates[@]} -eq 0 ]]; then
echo "$default_model"
return 0
fi
echo "" >&2
echo "[configure-guardrails] Choose paid profile primary model (non-free candidates):" >&2
for i in "${!candidates[@]}"; do
local n=$((i + 1))
local marker=""
if [[ "${candidates[$i]}" == "$default_model" ]]; then
marker=" (default)"
fi
printf " %d) %s%s\n" "$n" "${candidates[$i]}" "$marker" >&2
done
printf "Select number [default=%s]: " "$default_model" >&2
local choice=""
if ! IFS= read -r choice; then
echo "" >&2
echo "$default_model"
return 0
fi
if [[ -z "$choice" ]]; then
echo "$default_model"
return 0
fi
if [[ "$choice" =~ ^[0-9]+$ ]]; then
local idx=$((choice - 1))
if (( idx >= 0 && idx < ${#candidates[@]} )); then
echo "${candidates[$idx]}"
return 0
fi
fi
echo "[configure-guardrails] Invalid selection '$choice'; using default: $default_model" >&2
echo "$default_model"
}
copilot_models=()
while IFS= read -r model; do
[[ -z "$model" ]] && continue
copilot_models+=("$model")
done < <(
openclaw models list 2>/dev/null \
| awk 'NR>1 {print $1}' \
| grep '^github-copilot/' \
| awk '!seen[$0]++'
)
if [[ ${#copilot_models[@]} -eq 0 ]]; then
cat >&2 <<MSG
[configure-guardrails] No github-copilot models found.
Do this first on the target machine:
1) copilot auth login
2) openclaw models refresh || true
3) openclaw models list
Then re-run:
bash ./scripts/configure_copilot_guardrails_defaults.sh
MSG
exit 1
fi
prompt_mode="${PROMPT_FOR_PAID_MODEL:-auto}"
interactive_picker="false"
case "$prompt_mode" in
auto)
if [[ -t 0 && -t 1 ]]; then
interactive_picker="true"
fi
;;
true|yes|1)
interactive_picker="true"
;;
false|no|0)
interactive_picker="false"
;;
*)
echo "[configure-guardrails] Invalid PROMPT_FOR_PAID_MODEL='$prompt_mode' (expected auto/true/false). Using auto." >&2
if [[ -t 0 && -t 1 ]]; then
interactive_picker="true"
fi
;;
esac
if [[ "$interactive_picker" == "true" && ( ! -t 0 || ! -t 1 ) ]]; then
echo "[configure-guardrails] PROMPT_FOR_PAID_MODEL enabled but no interactive TTY; falling back to auto selection." >&2
interactive_picker="false"
fi
primary_model=""
for m in "${copilot_models[@]}"; do
if is_cheap_model "$m"; then
primary_model="$m"
break
fi
done
if [[ -z "$primary_model" ]]; then
primary_model="${copilot_models[0]}"
fi
# Build free/low-cost fallback candidates (excluding primary)
declare -a cheap_candidates=()
for m in "${copilot_models[@]}"; do
[[ "$m" == "$primary_model" ]] && continue
if is_cheap_model "$m"; then
cheap_candidates+=("$m")
fi
done
# If none matched cheap pattern, pick up to 2 non-primary models as fallback
if [[ ${#cheap_candidates[@]} -eq 0 ]]; then
for m in "${copilot_models[@]}"; do
[[ "$m" == "$primary_model" ]] && continue
cheap_candidates+=("$m")
[[ ${#cheap_candidates[@]} -ge 2 ]] && break
done
fi
# Keep at most 2 fallbacks
if [[ ${#cheap_candidates[@]} -gt 2 ]]; then
cheap_candidates=("${cheap_candidates[@]:0:2}")
fi
# High models: anything non-cheap and non-primary
declare -a high_models=()
for m in "${copilot_models[@]}"; do
[[ "$m" == "$primary_model" ]] && continue
if ! is_cheap_model "$m"; then
high_models+=("$m")
fi
done
# Ensure at least one high model if multiple exist
if [[ ${#high_models[@]} -eq 0 && ${#copilot_models[@]} -gt 1 ]]; then
for m in "${copilot_models[@]}"; do
[[ "$m" == "$primary_model" ]] && continue
high_models+=("$m")
break
done
fi
paid_model=""
if [[ ${#high_models[@]} -gt 0 ]]; then
paid_model="${high_models[0]}"
fi
if [[ -z "$paid_model" ]]; then
for m in "${copilot_models[@]}"; do
[[ "$m" == "$primary_model" ]] && continue
paid_model="$m"
break
done
fi
if [[ -z "$paid_model" ]]; then
paid_model="$primary_model"
fi
if [[ "$interactive_picker" == "true" && ${#high_models[@]} -gt 0 ]]; then
paid_model="$(prompt_for_paid_model "$paid_model" "${high_models[@]}")"
echo "[configure-guardrails] selected paid model: $paid_model"
fi
# free profile = lowest-cost primary + lowest-cost fallbacks
declare -a free_fallbacks=("${cheap_candidates[@]}")
# paid profile = strongest available model + cheap fallback chain
declare -a paid_fallbacks=()
if [[ "$primary_model" != "$paid_model" ]]; then
paid_fallbacks+=("$primary_model")
fi
for m in "${cheap_candidates[@]}"; do
[[ "$m" == "$paid_model" ]] && continue
skip="false"
for cur in "${paid_fallbacks[@]}"; do
if [[ "$cur" == "$m" ]]; then
skip="true"
break
fi
done
[[ "$skip" == "true" ]] && continue
paid_fallbacks+=("$m")
done
active_profile="paid"
current_hour="$(date +%H)"
current_hour=$((10#$current_hour))
if (( current_hour >= 18 || current_hour < 8 )); then
active_profile="free"
fi
active_primary="$paid_model"
active_fallbacks=("${paid_fallbacks[@]}")
if [[ "$active_profile" == "free" ]]; then
active_primary="$primary_model"
active_fallbacks=("${free_fallbacks[@]}")
fi
fallbacks_json="$(printf '%s\n' "${active_fallbacks[@]}" | jq -R . | jq -s .)"
high_json="$(printf '%s\n' "${high_models[@]}" | jq -R . | jq -s .)"
free_fallbacks_json="$(printf '%s\n' "${free_fallbacks[@]}" | jq -R . | jq -s .)"
paid_fallbacks_json="$(printf '%s\n' "${paid_fallbacks[@]}" | jq -R . | jq -s .)"
# Update configs
jq \
--arg primary "$active_primary" \
--argjson fallbacks "$fallbacks_json" \
'.enabled=true
| .autoFix=true
| .desiredPrimaryModel=$primary
| .enforceFallbackAllowlist=true
| .allowedFallbacks=$fallbacks
| .providerPolicy["github-copilot"]=true
| .providerPolicy["openai"]=false
| .providerPolicy["anthropic"]=false
| .providerPolicy["openrouter"]=false' \
"$POLICY_CONFIG" > "$POLICY_CONFIG.tmp" && mv "$POLICY_CONFIG.tmp" "$POLICY_CONFIG"
jq \
--arg low "$primary_model" \
--argjson highs "$high_json" \
'.enabled=true
| .lowModel=$low
| .highModels=$highs
| .warnAfterMinutes=2
| .revertAfterMinutes=45
| .minWarnIntervalMinutes=20' \
"$BUDGET_CONFIG" > "$BUDGET_CONFIG.tmp" && mv "$BUDGET_CONFIG.tmp" "$BUDGET_CONFIG"
jq '.enabled=true | .minAlertIntervalMinutes=30 | .checkOpenClawModelStatus=true' \
"$AUTH_CONFIG" > "$AUTH_CONFIG.tmp" && mv "$AUTH_CONFIG.tmp" "$AUTH_CONFIG"
jq \
--arg paid "$paid_model" \
--arg free "$primary_model" \
--argjson paid_fallbacks "$paid_fallbacks_json" \
--argjson free_fallbacks "$free_fallbacks_json" \
'.profiles.paid.description="Work-hours higher-quality Copilot model"
| .profiles.paid.primary=$paid
| .profiles.paid.fallbacks=$paid_fallbacks
| .profiles.paid.providerPolicy["github-copilot"]=true
| .profiles.paid.providerPolicy["openai"]=false
| .profiles.paid.providerPolicy["anthropic"]=false
| .profiles.paid.providerPolicy["openrouter"]=false
| .profiles.free.description="Off-hours low-cost/free Copilot model stack"
| .profiles.free.primary=$free
| .profiles.free.fallbacks=$free_fallbacks
| .profiles.free.providerPolicy["github-copilot"]=true
| .profiles.free.providerPolicy["openai"]=false
| .profiles.free.providerPolicy["anthropic"]=false
| .profiles.free.providerPolicy["openrouter"]=false' \
"$MODEL_PROFILES_CONFIG" > "$MODEL_PROFILES_CONFIG.tmp" && mv "$MODEL_PROFILES_CONFIG.tmp" "$MODEL_PROFILES_CONFIG"
jq \
'.enabled=true
| .dayProfile="paid"
| .nightProfile="free"
| .dayStartHour=(.dayStartHour // 8)
| .nightStartHour=(.nightStartHour // 18)
| .sessionKey=(.sessionKey // "agent:main:main")
| .switchScript=(.switchScript // "./scripts/model_profile_switch.sh")
| .stateFile=(.stateFile // "~/.openclaw/model-schedule-state.json")' \
"$MODEL_SCHEDULE_CONFIG" > "$MODEL_SCHEDULE_CONFIG.tmp" && mv "$MODEL_SCHEDULE_CONFIG.tmp" "$MODEL_SCHEDULE_CONFIG"
# Apply live policy/routing on target machine
openclaw models set "$active_primary" >/dev/null
openclaw models fallbacks clear >/dev/null
for fb in "${active_fallbacks[@]}"; do
openclaw models fallbacks add "$fb" >/dev/null
done
openclaw config set --json providers.github-copilot.enabled true >/dev/null
openclaw config set --json providers.openai.enabled false >/dev/null
openclaw config set --json providers.anthropic.enabled false >/dev/null
openclaw config set --json providers.openrouter.enabled false >/dev/null
echo "Configured Copilot guardrails defaults:"
echo " active_profile_now: $active_profile"
echo " active_primary_model: $active_primary"
if [[ ${#active_fallbacks[@]} -gt 0 ]]; then
echo " active_fallbacks: ${active_fallbacks[*]}"
else
echo " active_fallbacks: (none)"
fi
echo " paid_profile_primary: $paid_model"
if [[ ${#paid_fallbacks[@]} -gt 0 ]]; then
echo " paid_profile_fallbacks: ${paid_fallbacks[*]}"
else
echo " paid_profile_fallbacks: (none)"
fi
echo " free_profile_primary: $primary_model"
if [[ ${#free_fallbacks[@]} -gt 0 ]]; then
echo " free_profile_fallbacks: ${free_fallbacks[*]}"
else
echo " free_profile_fallbacks: (none)"
fi
echo ""
echo "Applied live routing/provider policy and updated config files:"
echo " $POLICY_CONFIG"
echo " $BUDGET_CONFIG"
echo " $AUTH_CONFIG"
echo " $MODEL_PROFILES_CONFIG"
echo " $MODEL_SCHEDULE_CONFIG"

View File

@ -1,134 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_PATH="${AUTH_WATCHDOG_CONFIG:-$ROOT_DIR/config/copilot-auth-watchdog.config.json}"
if ! command -v jq >/dev/null 2>&1; then
echo "[copilot-auth-watchdog] jq 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
}
now_ms() {
python3 - <<'PY'
import time
print(int(time.time()*1000))
PY
}
send_notice() {
local text="$1"
local last_channel="$2"
local last_to="$3"
if [[ -z "$last_channel" || -z "$last_to" ]]; then
echo "[copilot-auth-watchdog] notice skipped (missing channel/target): $text" >&2
return 0
fi
local target="$last_to"
if [[ "$target" == *:* ]]; then
target="${target#*:}"
fi
openclaw message send \
--channel "$last_channel" \
--target "$target" \
--message "$text" \
>/dev/null 2>&1 || true
}
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "[copilot-auth-watchdog] missing config: $CONFIG_PATH" >&2
exit 1
fi
enabled="$(jq -r '.enabled // true' "$CONFIG_PATH")"
if [[ "$enabled" != "true" ]]; then
exit 0
fi
session_key="$(jq -r '.sessionKey // "agent:main:main"' "$CONFIG_PATH")"
alert_interval_min="$(jq -r '.minAlertIntervalMinutes // 30' "$CONFIG_PATH")"
state_file_raw="$(jq -r '.stateFile // "~/.openclaw/copilot-auth-watchdog-state.json"' "$CONFIG_PATH")"
state_file="$(expand_tilde "$state_file_raw")"
check_model_status="$(jq -r '.checkOpenClawModelStatus // true' "$CONFIG_PATH")"
state_dir="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
sessions_file="$state_dir/agents/main/sessions/sessions.json"
last_channel=""
last_to=""
if [[ -r "$sessions_file" ]]; then
session_json="$(jq -c --arg key "$session_key" '.[$key] // {}' "$sessions_file" 2>/dev/null || true)"
if [[ -n "$session_json" && "$session_json" != "{}" ]]; then
last_channel="$(jq -r '.lastChannel // empty' <<<"$session_json")"
last_to="$(jq -r '.lastTo // empty' <<<"$session_json")"
fi
fi
declare -a issues=()
if ! command -v copilot >/dev/null 2>&1; then
issues+=("copilot CLI not installed; run setup_openclaw_copilot.sh")
else
set +e
copilot_out="$(copilot auth status 2>&1)"
copilot_rc=$?
set -e
if [[ $copilot_rc -ne 0 ]]; then
issues+=("copilot auth status failed; run: copilot auth login")
elif echo "$copilot_out" | grep -Eiq 'not authenticated|not logged|login required|expired|unauthorized|invalid'; then
issues+=("copilot auth not healthy; run: copilot auth login")
fi
fi
if [[ "$check_model_status" == "true" ]]; then
if ! command -v openclaw >/dev/null 2>&1; then
issues+=("openclaw CLI not installed")
else
if ! openclaw models status --check >/dev/null 2>&1; then
issues+=("openclaw model auth check failed; run: openclaw models status --json and copilot auth login")
fi
fi
fi
if [[ ${#issues[@]} -eq 0 ]]; then
exit 0
fi
mkdir -p "$(dirname "$state_file")"
if [[ ! -f "$state_file" ]]; then
printf '{}\n' > "$state_file"
fi
state_json="$(cat "$state_file")"
last_alert_at="$(jq -r --arg key "$session_key" '.[$key].lastAlertAt // 0' <<<"$state_json")"
if [[ ! "$last_alert_at" =~ ^[0-9]+$ ]]; then
last_alert_at=0
fi
now="$(now_ms)"
interval_ms=$((alert_interval_min * 60 * 1000))
if (( now - last_alert_at < interval_ms )); then
exit 0
fi
summary="Copilot auth watchdog alert: ${issues[*]}"
echo "[copilot-auth-watchdog] $summary"
send_notice "$summary" "$last_channel" "$last_to"
state_json="$(jq --arg key "$session_key" --argjson now "$now" '.[$key].lastAlertAt=$now' <<<"$state_json")"
printf '%s\n' "$state_json" > "$state_file"

View File

@ -1,235 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_PATH="${POLICY_GUARD_CONFIG:-$ROOT_DIR/config/copilot-policy-guard.config.json}"
if ! command -v jq >/dev/null 2>&1; then
echo "[copilot-policy-guard] jq is required" >&2
exit 1
fi
if ! command -v openclaw >/dev/null 2>&1; then
echo "[copilot-policy-guard] 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
}
now_ms() {
python3 - <<'PY'
import time
print(int(time.time()*1000))
PY
}
send_notice() {
local text="$1"
local last_channel="$2"
local last_to="$3"
if [[ -z "$last_channel" || -z "$last_to" ]]; then
echo "[copilot-policy-guard] notice skipped (missing channel/target): $text" >&2
return 0
fi
local target="$last_to"
if [[ "$target" == *:* ]]; then
target="${target#*:}"
fi
openclaw message send \
--channel "$last_channel" \
--target "$target" \
--message "$text" \
>/dev/null 2>&1 || true
}
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "[copilot-policy-guard] missing config: $CONFIG_PATH" >&2
exit 1
fi
enabled="$(jq -r '.enabled // true' "$CONFIG_PATH")"
if [[ "$enabled" != "true" ]]; then
exit 0
fi
auto_fix="$(jq -r '.autoFix // true' "$CONFIG_PATH")"
session_key="$(jq -r '.sessionKey // "agent:main:main"' "$CONFIG_PATH")"
alert_interval_min="$(jq -r '.minAlertIntervalMinutes // 20' "$CONFIG_PATH")"
state_file_raw="$(jq -r '.stateFile // "~/.openclaw/copilot-policy-guard-state.json"' "$CONFIG_PATH")"
state_file="$(expand_tilde "$state_file_raw")"
required_primary_prefix="$(jq -r '.requiredPrimaryPrefix // "github-copilot/"' "$CONFIG_PATH")"
required_fallback_prefix="$(jq -r '.requiredFallbackPrefix // "github-copilot/"' "$CONFIG_PATH")"
desired_primary_model="$(jq -r '.desiredPrimaryModel // empty' "$CONFIG_PATH")"
enforce_allowlist="$(jq -r '.enforceFallbackAllowlist // false' "$CONFIG_PATH")"
state_dir="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
sessions_file="$state_dir/agents/main/sessions/sessions.json"
last_channel=""
last_to=""
if [[ -r "$sessions_file" ]]; then
session_json="$(jq -c --arg key "$session_key" '.[$key] // {}' "$sessions_file" 2>/dev/null || true)"
if [[ -n "$session_json" && "$session_json" != "{}" ]]; then
last_channel="$(jq -r '.lastChannel // empty' <<<"$session_json")"
last_to="$(jq -r '.lastTo // empty' <<<"$session_json")"
fi
fi
declare -a issues=()
declare -a fixes=()
models_json="$(openclaw models status --json 2>/dev/null || true)"
if [[ -z "$models_json" ]]; then
issues+=("openclaw models status --json failed")
else
default_model="$(jq -r '.defaultModel // empty' <<<"$models_json")"
if [[ -n "$default_model" && "$default_model" != ${required_primary_prefix}* ]]; then
issues+=("primary model is not Copilot: $default_model")
if [[ "$auto_fix" == "true" && -n "$desired_primary_model" ]]; then
if openclaw models set "$desired_primary_model" >/dev/null 2>&1; then
fixes+=("set primary model to $desired_primary_model")
else
issues+=("failed to set primary model to $desired_primary_model")
fi
fi
fi
fallbacks=()
while IFS= read -r fb; do
[[ -z "$fb" ]] && continue
fallbacks+=("$fb")
done < <(jq -r '.fallbacks[]? // empty' <<<"$models_json")
bad_prefix=0
bad_allowlist=0
for fb in "${fallbacks[@]}"; do
if [[ "$fb" != ${required_fallback_prefix}* ]]; then
bad_prefix=1
issues+=("fallback is not Copilot: $fb")
fi
done
if [[ "$enforce_allowlist" == "true" ]]; then
allowed_fallbacks=()
while IFS= read -r af; do
[[ -z "$af" ]] && continue
allowed_fallbacks+=("$af")
done < <(jq -r '.allowedFallbacks[]? // empty' "$CONFIG_PATH")
if [[ ${#allowed_fallbacks[@]} -gt 0 ]]; then
for fb in "${fallbacks[@]}"; do
found=0
for af in "${allowed_fallbacks[@]}"; do
if [[ "$fb" == "$af" ]]; then
found=1
break
fi
done
if [[ $found -eq 0 ]]; then
bad_allowlist=1
issues+=("fallback not in allowlist: $fb")
fi
done
fi
fi
if [[ "$auto_fix" == "true" && ( $bad_prefix -eq 1 || $bad_allowlist -eq 1 ) ]]; then
desired_fallbacks=()
if [[ "$enforce_allowlist" == "true" ]]; then
while IFS= read -r af; do
[[ -n "$af" ]] && desired_fallbacks+=("$af")
done < <(jq -r '.allowedFallbacks[]? // empty' "$CONFIG_PATH")
fi
if [[ ${#desired_fallbacks[@]} -eq 0 ]]; then
for fb in "${fallbacks[@]}"; do
if [[ "$fb" == ${required_fallback_prefix}* ]]; then
desired_fallbacks+=("$fb")
fi
done
fi
if openclaw models fallbacks clear >/dev/null 2>&1; then
fixes+=("cleared fallback list")
for fb in "${desired_fallbacks[@]}"; do
if openclaw models fallbacks add "$fb" >/dev/null 2>&1; then
fixes+=("added fallback $fb")
else
issues+=("failed to add fallback $fb")
fi
done
else
issues+=("failed to clear fallback list")
fi
fi
fi
while IFS='=' read -r provider desired; do
[[ -z "$provider" ]] && continue
desired_bool="false"
if [[ "$desired" == "true" ]]; then
desired_bool="true"
fi
current="$(openclaw config get "providers.${provider}.enabled" 2>/dev/null || true)"
current_trim="$(printf '%s' "$current" | tr -d '[:space:]')"
if [[ "$current_trim" != "$desired_bool" ]]; then
issues+=("provider policy drift: providers.${provider}.enabled expected ${desired_bool}, got ${current_trim:-unset}")
if [[ "$auto_fix" == "true" ]]; then
if openclaw config set --json "providers.${provider}.enabled" "$desired_bool" >/dev/null 2>&1; then
fixes+=("set providers.${provider}.enabled=${desired_bool}")
else
issues+=("failed to set providers.${provider}.enabled=${desired_bool}")
fi
fi
fi
done < <(jq -r '.providerPolicy // {} | to_entries[] | "\(.key)=\(.value)"' "$CONFIG_PATH")
if [[ ${#issues[@]} -eq 0 && ${#fixes[@]} -eq 0 ]]; then
exit 0
fi
mkdir -p "$(dirname "$state_file")"
if [[ ! -f "$state_file" ]]; then
printf '{}\n' > "$state_file"
fi
state_json="$(cat "$state_file")"
last_alert_at="$(jq -r --arg key "$session_key" '.[$key].lastAlertAt // 0' <<<"$state_json")"
if [[ ! "$last_alert_at" =~ ^[0-9]+$ ]]; then
last_alert_at=0
fi
now="$(now_ms)"
interval_ms=$((alert_interval_min * 60 * 1000))
should_alert="false"
if (( now - last_alert_at >= interval_ms )); then
should_alert="true"
fi
summary=""
if [[ ${#fixes[@]} -gt 0 ]]; then
summary="Copilot policy guard auto-fixed: ${fixes[*]}"
else
summary="Copilot policy drift detected: ${issues[*]}"
fi
echo "[copilot-policy-guard] $summary"
if [[ "$should_alert" == "true" ]]; then
send_notice "$summary" "$last_channel" "$last_to"
state_json="$(jq --arg key "$session_key" --argjson now "$now" '.[$key].lastAlertAt=$now' <<<"$state_json")"
printf '%s\n' "$state_json" > "$state_file"
fi

View File

@ -1,100 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
log_info() { echo "[finalize] $*"; }
log_warn() { echo "[finalize] WARN: $*" >&2; }
log_err() { echo "[finalize] ERROR: $*" >&2; }
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
log_err "Missing required command: $cmd"
exit 1
fi
}
copilot_authed() {
copilot auth status >/dev/null 2>&1
}
refresh_models() {
openclaw models refresh >/dev/null 2>&1 || true
}
copilot_model_count() {
openclaw models list 2>/dev/null \
| awk 'NR>1 && /^github-copilot\// {count++} END {print count+0}'
}
require_cmd openclaw
require_cmd copilot
require_cmd jq
if ! copilot_authed; then
log_warn "Copilot auth is not active."
if [[ -t 0 ]]; then
log_info "Launching interactive login: copilot auth login"
copilot auth login
else
log_err "Non-interactive shell cannot complete browser login."
log_err "Run: copilot auth login"
exit 1
fi
fi
if ! copilot_authed; then
log_err "Copilot auth still not active after login."
log_err "Run 'copilot auth status' and resolve auth issues, then retry."
exit 1
fi
log_info "Refreshing model catalog..."
refresh_models
count="$(copilot_model_count)"
if [[ "$count" -eq 0 ]]; then
log_warn "No github-copilot models detected yet. Restarting gateway and retrying once."
openclaw gateway restart >/dev/null 2>&1 || true
sleep 1
refresh_models
count="$(copilot_model_count)"
fi
if [[ "$count" -eq 0 ]]; then
log_err "No github-copilot models available to configure."
log_err "Check entitlement/auth, then rerun this script."
openclaw models list || true
exit 1
fi
log_info "Detected $count Copilot model(s). Installing guardrails + model profiles..."
bash "$SCRIPT_DIR/install_copilot_guardrails.sh"
log_info "Enabling recommended hooks..."
openclaw hooks enable boot-md >/dev/null 2>&1 || true
openclaw hooks enable command-logger >/dev/null 2>&1 || true
openclaw hooks enable session-memory >/dev/null 2>&1 || true
log_info "Restarting gateway..."
openclaw gateway restart >/dev/null 2>&1 || true
log_info "Final status checks:"
copilot auth status || true
openclaw models status || true
for label in \
ai.openclaw.model-budget-guard \
ai.openclaw.copilot-policy-guard \
ai.openclaw.copilot-auth-watchdog \
ai.openclaw.copilot-model-schedule-guard; do
if launchctl print "gui/$(id -u)/$label" >/dev/null 2>&1; then
echo "[finalize] launchd OK: $label"
else
echo "[finalize] launchd WARN: $label not loaded"
fi
done
log_info "Done. Copilot setup is fully configured."

View File

@ -1,78 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if ! command -v jq >/dev/null 2>&1; then
echo "[install-copilot-auth-watchdog] jq is required" >&2
exit 1
fi
LABEL="ai.openclaw.copilot-auth-watchdog"
PLIST_PATH="$HOME/Library/LaunchAgents/$LABEL.plist"
STAGE_DIR="${MODEL_GUARD_STAGE_DIR:-$HOME/Library/Application Support/openclaw-copilot-guard}"
STAGE_SCRIPTS_DIR="$STAGE_DIR/scripts"
STAGE_CONFIG="$STAGE_DIR/copilot-auth-watchdog.config.json"
AUTH_WATCHDOG_CONFIG="${AUTH_WATCHDOG_CONFIG:-$STAGE_CONFIG}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-300}"
mkdir -p "$STAGE_SCRIPTS_DIR"
cp "$ROOT_DIR/scripts/copilot_auth_watchdog.sh" "$STAGE_SCRIPTS_DIR/copilot_auth_watchdog.sh"
jq \
--arg state "$STAGE_DIR/copilot-auth-watchdog-state.json" \
'.stateFile=$state' \
"$ROOT_DIR/config/copilot-auth-watchdog.config.json" > "$STAGE_CONFIG"
chmod +x "$STAGE_SCRIPTS_DIR/copilot_auth_watchdog.sh"
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$STAGE_SCRIPTS_DIR/copilot_auth_watchdog.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>AUTH_WATCHDOG_CONFIG</key>
<string>$AUTH_WATCHDOG_CONFIG</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>$INTERVAL_SECONDS</integer>
<key>StandardOutPath</key>
<string>/tmp/openclaw-copilot-auth-watchdog.log</string>
<key>StandardErrorPath</key>
<string>/tmp/openclaw-copilot-auth-watchdog.err.log</string>
</dict>
</plist>
PLIST
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl kickstart -k "gui/$(id -u)/$LABEL"
cat <<MSG
Installed LaunchAgent:
$PLIST_PATH
Staged runtime files:
$STAGE_DIR
Check status:
launchctl print gui/$(id -u)/$LABEL
tail -f /tmp/openclaw-copilot-auth-watchdog.log /tmp/openclaw-copilot-auth-watchdog.err.log
MSG

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Detect available Copilot models, choose low-cost defaults, and apply policy.
bash "$SCRIPT_DIR/configure_copilot_guardrails_defaults.sh"
# Install launchd jobs.
bash "$SCRIPT_DIR/install_model_budget_guard_launchd.sh"
bash "$SCRIPT_DIR/install_copilot_policy_guard_launchd.sh"
bash "$SCRIPT_DIR/install_copilot_auth_watchdog_launchd.sh"
bash "$SCRIPT_DIR/install_model_schedule_guard_launchd.sh"
echo "All Copilot guardrails configured and installed (budget + policy + auth + schedule)."

View File

@ -1,78 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if ! command -v jq >/dev/null 2>&1; then
echo "[install-copilot-policy-guard] jq is required" >&2
exit 1
fi
LABEL="ai.openclaw.copilot-policy-guard"
PLIST_PATH="$HOME/Library/LaunchAgents/$LABEL.plist"
STAGE_DIR="${MODEL_GUARD_STAGE_DIR:-$HOME/Library/Application Support/openclaw-copilot-guard}"
STAGE_SCRIPTS_DIR="$STAGE_DIR/scripts"
STAGE_CONFIG="$STAGE_DIR/copilot-policy-guard.config.json"
POLICY_GUARD_CONFIG="${POLICY_GUARD_CONFIG:-$STAGE_CONFIG}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-180}"
mkdir -p "$STAGE_SCRIPTS_DIR"
cp "$ROOT_DIR/scripts/copilot_policy_guard.sh" "$STAGE_SCRIPTS_DIR/copilot_policy_guard.sh"
jq \
--arg state "$STAGE_DIR/copilot-policy-guard-state.json" \
'.stateFile=$state' \
"$ROOT_DIR/config/copilot-policy-guard.config.json" > "$STAGE_CONFIG"
chmod +x "$STAGE_SCRIPTS_DIR/copilot_policy_guard.sh"
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$STAGE_SCRIPTS_DIR/copilot_policy_guard.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>POLICY_GUARD_CONFIG</key>
<string>$POLICY_GUARD_CONFIG</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>$INTERVAL_SECONDS</integer>
<key>StandardOutPath</key>
<string>/tmp/openclaw-copilot-policy-guard.log</string>
<key>StandardErrorPath</key>
<string>/tmp/openclaw-copilot-policy-guard.err.log</string>
</dict>
</plist>
PLIST
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl kickstart -k "gui/$(id -u)/$LABEL"
cat <<MSG
Installed LaunchAgent:
$PLIST_PATH
Staged runtime files:
$STAGE_DIR
Check status:
launchctl print gui/$(id -u)/$LABEL
tail -f /tmp/openclaw-copilot-policy-guard.log /tmp/openclaw-copilot-policy-guard.err.log
MSG

View File

@ -1,78 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if ! command -v jq >/dev/null 2>&1; then
echo "[install-model-budget-guard] jq is required" >&2
exit 1
fi
LABEL="ai.openclaw.model-budget-guard"
PLIST_PATH="$HOME/Library/LaunchAgents/$LABEL.plist"
STAGE_DIR="${MODEL_GUARD_STAGE_DIR:-$HOME/Library/Application Support/openclaw-copilot-guard}"
STAGE_SCRIPTS_DIR="$STAGE_DIR/scripts"
STAGE_CONFIG="$STAGE_DIR/model-budget-guard.config.json"
MODEL_GUARD_CONFIG="${MODEL_GUARD_CONFIG:-$STAGE_CONFIG}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-120}"
mkdir -p "$STAGE_SCRIPTS_DIR"
cp "$ROOT_DIR/scripts/model_budget_guard.sh" "$STAGE_SCRIPTS_DIR/model_budget_guard.sh"
jq \
--arg state "$STAGE_DIR/model-budget-guard-state.json" \
'.stateFile=$state' \
"$ROOT_DIR/config/model-budget-guard.config.json" > "$STAGE_CONFIG"
chmod +x "$STAGE_SCRIPTS_DIR/model_budget_guard.sh"
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$STAGE_SCRIPTS_DIR/model_budget_guard.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>MODEL_GUARD_CONFIG</key>
<string>$MODEL_GUARD_CONFIG</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>$INTERVAL_SECONDS</integer>
<key>StandardOutPath</key>
<string>/tmp/openclaw-model-budget-guard.log</string>
<key>StandardErrorPath</key>
<string>/tmp/openclaw-model-budget-guard.err.log</string>
</dict>
</plist>
PLIST
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl kickstart -k "gui/$(id -u)/$LABEL"
cat <<MSG
Installed LaunchAgent:
$PLIST_PATH
Staged runtime files:
$STAGE_DIR
Check status:
launchctl print gui/$(id -u)/$LABEL
tail -f /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
MSG

View File

@ -1,86 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if ! command -v jq >/dev/null 2>&1; then
echo "[install-model-schedule-guard] jq is required" >&2
exit 1
fi
LABEL="ai.openclaw.copilot-model-schedule-guard"
PLIST_PATH="$HOME/Library/LaunchAgents/$LABEL.plist"
STAGE_DIR="${MODEL_GUARD_STAGE_DIR:-$HOME/Library/Application Support/openclaw-copilot-guard}"
STAGE_SCRIPTS_DIR="$STAGE_DIR/scripts"
STAGE_SCHEDULE_CONFIG="$STAGE_DIR/model-schedule.config.json"
STAGE_PROFILES_CONFIG="$STAGE_DIR/model-profiles.config.json"
STAGE_POLICY_CONFIG="$STAGE_DIR/copilot-policy-guard.config.json"
MODEL_SCHEDULE_CONFIG="${MODEL_SCHEDULE_CONFIG:-$STAGE_SCHEDULE_CONFIG}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-300}"
mkdir -p "$STAGE_SCRIPTS_DIR"
cp "$ROOT_DIR/scripts/model_schedule_guard.sh" "$STAGE_SCRIPTS_DIR/model_schedule_guard.sh"
cp "$ROOT_DIR/scripts/model_profile_switch.sh" "$STAGE_SCRIPTS_DIR/model_profile_switch.sh"
cp "$ROOT_DIR/config/model-profiles.config.json" "$STAGE_PROFILES_CONFIG"
jq \
--arg state "$STAGE_DIR/model-schedule-state.json" \
'.stateFile=$state' \
"$ROOT_DIR/config/model-schedule.config.json" > "$STAGE_SCHEDULE_CONFIG"
chmod +x "$STAGE_SCRIPTS_DIR/model_schedule_guard.sh" "$STAGE_SCRIPTS_DIR/model_profile_switch.sh"
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$STAGE_SCRIPTS_DIR/model_schedule_guard.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>MODEL_SCHEDULE_CONFIG</key>
<string>$MODEL_SCHEDULE_CONFIG</string>
<key>MODEL_PROFILES_CONFIG</key>
<string>$STAGE_PROFILES_CONFIG</string>
<key>POLICY_GUARD_CONFIG</key>
<string>$STAGE_POLICY_CONFIG</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>$INTERVAL_SECONDS</integer>
<key>StandardOutPath</key>
<string>/tmp/openclaw-copilot-model-schedule-guard.log</string>
<key>StandardErrorPath</key>
<string>/tmp/openclaw-copilot-model-schedule-guard.err.log</string>
</dict>
</plist>
PLIST
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
launchctl kickstart -k "gui/$(id -u)/$LABEL"
cat <<MSG
Installed LaunchAgent:
$PLIST_PATH
Staged runtime files:
$STAGE_DIR
Check status:
launchctl print gui/$(id -u)/$LABEL
tail -f /tmp/openclaw-copilot-model-schedule-guard.log /tmp/openclaw-copilot-model-schedule-guard.err.log
MSG

View File

@ -1,202 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_PATH="${MODEL_GUARD_CONFIG:-$ROOT_DIR/config/model-budget-guard.config.json}"
if ! command -v jq >/dev/null 2>&1; then
echo "[model-guard] jq is required" >&2
exit 1
fi
if ! command -v openclaw >/dev/null 2>&1; then
echo "[model-guard] 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
}
safe_now_ms() {
python3 - <<'PY'
import time
print(int(time.time()*1000))
PY
}
send_notice() {
local text="$1"
local last_channel="$2"
local last_to="$3"
if [[ -z "$last_channel" || -z "$last_to" ]]; then
echo "[model-guard] notice skipped (missing channel/target): $text" >&2
return 0
fi
local target="$last_to"
if [[ "$target" == *:* ]]; then
target="${target#*:}"
fi
openclaw message send \
--channel "$last_channel" \
--target "$target" \
--message "$text" \
>/dev/null 2>&1 || true
}
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "[model-guard] missing config: $CONFIG_PATH" >&2
exit 1
fi
enabled="$(jq -r '.enabled // true' "$CONFIG_PATH")"
if [[ "$enabled" != "true" ]]; then
exit 0
fi
session_key="$(jq -r '.sessionKey // "agent:main:main"' "$CONFIG_PATH")"
low_model="$(jq -r '.lowModel // empty' "$CONFIG_PATH")"
warn_after_min="$(jq -r '.warnAfterMinutes // 2' "$CONFIG_PATH")"
revert_after_min="$(jq -r '.revertAfterMinutes // 45' "$CONFIG_PATH")"
min_warn_interval_min="$(jq -r '.minWarnIntervalMinutes // 20' "$CONFIG_PATH")"
state_file_raw="$(jq -r '.stateFile // "~/.openclaw/model-budget-guard-state.json"' "$CONFIG_PATH")"
state_file="$(expand_tilde "$state_file_raw")"
if [[ -z "$low_model" ]]; then
echo "[model-guard] lowModel must be set" >&2
exit 1
fi
state_dir="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
sessions_file="$state_dir/agents/main/sessions/sessions.json"
if [[ ! -r "$sessions_file" ]]; then
echo "[model-guard] sessions file not found: $sessions_file" >&2
exit 0
fi
session_json="$(jq -c --arg key "$session_key" '.[$key] // {}' "$sessions_file" 2>/dev/null || true)"
if [[ -z "$session_json" || "$session_json" == "{}" ]]; then
exit 0
fi
session_id="$(jq -r '.sessionId // empty' <<<"$session_json")"
updated_at="$(jq -r '.updatedAt // 0' <<<"$session_json")"
model_raw="$(jq -r '.model // empty' <<<"$session_json")"
provider_raw="$(jq -r '.provider // empty' <<<"$session_json")"
last_channel="$(jq -r '.lastChannel // empty' <<<"$session_json")"
last_to="$(jq -r '.lastTo // empty' <<<"$session_json")"
if [[ -z "$model_raw" ]]; then
exit 0
fi
model_full="$model_raw"
if [[ "$model_raw" != */* && -n "$provider_raw" && "$provider_raw" != "null" ]]; then
model_full="$provider_raw/$model_raw"
fi
high_match="$(jq -r --arg m "$model_full" '.highModels // [] | index($m) != null' "$CONFIG_PATH")"
if [[ "$high_match" != "true" ]]; then
exit 0
fi
if [[ ! "$updated_at" =~ ^[0-9]+$ ]]; then
exit 0
fi
now_ms="$(safe_now_ms)"
age_ms=$((now_ms - updated_at))
if (( age_ms < 0 )); then age_ms=0; fi
warn_after_ms=$((warn_after_min * 60 * 1000))
revert_after_ms=$((revert_after_min * 60 * 1000))
min_warn_interval_ms=$((min_warn_interval_min * 60 * 1000))
mkdir -p "$(dirname "$state_file")"
if [[ ! -f "$state_file" ]]; then
cat > "$state_file" <<'JSON'
{}
JSON
fi
state_json="$(cat "$state_file")"
last_warned_model="$(jq -r --arg key "$session_key" '.[$key].lastWarnedModel // empty' <<<"$state_json")"
last_warned_session="$(jq -r --arg key "$session_key" '.[$key].lastWarnedSessionId // empty' <<<"$state_json")"
last_warn_at="$(jq -r --arg key "$session_key" '.[$key].lastWarnAt // 0' <<<"$state_json")"
last_revert_at="$(jq -r --arg key "$session_key" '.[$key].lastRevertAt // 0' <<<"$state_json")"
should_warn="false"
if (( age_ms >= warn_after_ms )); then
if [[ "$last_warned_model" != "$model_full" || "$last_warned_session" != "$session_id" ]]; then
should_warn="true"
elif [[ "$last_warn_at" =~ ^[0-9]+$ ]] && (( now_ms - last_warn_at >= min_warn_interval_ms )); then
should_warn="true"
fi
fi
if [[ "$should_warn" == "true" ]]; then
send_notice "Heads up: high-cost model active ($model_full). Switch back after heavy work: /model $low_model" "$last_channel" "$last_to"
state_json="$(jq \
--arg key "$session_key" \
--arg model "$model_full" \
--arg sid "$session_id" \
--argjson now "$now_ms" \
'.[$key].lastWarnedModel=$model | .[$key].lastWarnedSessionId=$sid | .[$key].lastWarnAt=$now' \
<<<"$state_json")"
fi
if (( age_ms >= revert_after_ms )); then
can_revert="true"
if [[ "$last_revert_at" =~ ^[0-9]+$ ]] && (( now_ms - last_revert_at < min_warn_interval_ms )); then
can_revert="false"
fi
if [[ "$can_revert" == "true" && -n "$session_id" ]]; then
python3 - "$session_id" "$low_model" <<'PY'
import subprocess
import sys
session_id = sys.argv[1]
low_model = sys.argv[2]
cmd = [
"openclaw",
"agent",
"--session-id", session_id,
"--channel", "last",
"--message", f"/model {low_model}",
]
try:
subprocess.run(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
timeout=20,
)
except subprocess.TimeoutExpired:
pass
PY
send_notice "Auto-switched back to $low_model to avoid high-model quota burn. Use high model only for deep tasks." "$last_channel" "$last_to"
state_json="$(jq \
--arg key "$session_key" \
--arg model "$low_model" \
--arg sid "$session_id" \
--argjson now "$now_ms" \
'.[$key].lastRevertAt=$now | .[$key].lastWarnedModel=$model | .[$key].lastWarnedSessionId=$sid | .[$key].lastWarnAt=$now' \
<<<"$state_json")"
fi
fi
printf '%s\n' "$state_json" > "$state_file"

View File

@ -1,186 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_PATH="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}"
POLICY_CONFIG_PATH="${POLICY_GUARD_CONFIG:-$ROOT_DIR/config/copilot-policy-guard.config.json}"
SESSION_KEY="${MODEL_SWITCH_SESSION_KEY:-agent:main:main}"
if ! command -v jq >/dev/null 2>&1; then
echo "[model-switch] jq is required" >&2
exit 1
fi
if ! command -v openclaw >/dev/null 2>&1; then
echo "[model-switch] openclaw CLI is required" >&2
exit 1
fi
usage() {
cat <<'USAGE'
Usage:
bash ./scripts/model_profile_switch.sh status
bash ./scripts/model_profile_switch.sh <profile> [--no-live] [--no-status]
Examples:
bash ./scripts/model_profile_switch.sh free
bash ./scripts/model_profile_switch.sh paid --no-live
USAGE
}
get_session_id() {
local session_key="$1"
local state_dir="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
local sessions_file="$state_dir/agents/main/sessions/sessions.json"
if [[ ! -f "$sessions_file" ]]; then
return 0
fi
jq -r --arg key "$session_key" '.[$key].sessionId // empty' "$sessions_file" 2>/dev/null || true
}
sync_policy_config() {
local primary="$1"
shift
local -a fb_models=("$@")
if [[ ! -f "$POLICY_CONFIG_PATH" ]]; then
return 0
fi
local fallbacks_json
if [[ ${#fb_models[@]} -eq 0 ]]; then
fallbacks_json="[]"
else
fallbacks_json="$(printf '%s\n' "${fb_models[@]}" | jq -R . | jq -s .)"
fi
if jq \
--arg primary "$primary" \
--argjson fallbacks "$fallbacks_json" \
'.desiredPrimaryModel=$primary | .allowedFallbacks=$fallbacks | .enforceFallbackAllowlist=true' \
"$POLICY_CONFIG_PATH" > "$POLICY_CONFIG_PATH.tmp"; then
mv "$POLICY_CONFIG_PATH.tmp" "$POLICY_CONFIG_PATH" || true
else
rm -f "$POLICY_CONFIG_PATH.tmp"
fi
}
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "[model-switch] missing config: $CONFIG_PATH" >&2
exit 1
fi
profile="${1:-}"
if [[ -z "$profile" ]]; then
usage
exit 1
fi
shift || true
apply_live="true"
show_status="true"
while (( "$#" )); do
case "$1" in
--no-live) apply_live="false" ;;
--no-status) show_status="false" ;;
-h|--help)
usage
exit 0
;;
*)
echo "[model-switch] unknown argument: $1" >&2
usage
exit 1
;;
esac
shift
done
if [[ "$profile" == "status" ]]; then
echo "Available profiles:"
jq -r '.profiles | to_entries[] | "- \(.key): \(.value.primary // "(unset)") (\(.value.description // "no description"))"' "$CONFIG_PATH"
echo ""
openclaw models status
exit 0
fi
profile_exists="$(jq -r --arg p "$profile" '.profiles[$p] != null' "$CONFIG_PATH")"
if [[ "$profile_exists" != "true" ]]; then
echo "[model-switch] unknown profile '$profile' in $CONFIG_PATH" >&2
jq -r '.profiles | keys[]' "$CONFIG_PATH" | sed 's/^/ - /'
exit 1
fi
primary="$(jq -r --arg p "$profile" '.profiles[$p].primary // empty' "$CONFIG_PATH")"
if [[ -z "$primary" ]]; then
echo "[model-switch] profile '$profile' has no primary model configured" >&2
exit 1
fi
fallbacks=()
while IFS= read -r fb; do
[[ -z "$fb" ]] && continue
fallbacks+=("$fb")
done < <(jq -r --arg p "$profile" '.profiles[$p].fallbacks[]? // empty' "$CONFIG_PATH")
echo "[model-switch] Applying profile: $profile"
echo "[model-switch] primary: $primary"
if [[ ${#fallbacks[@]} -gt 0 ]]; then
echo "[model-switch] fallbacks: ${fallbacks[*]}"
else
echo "[model-switch] fallbacks: (none)"
fi
openclaw models set "$primary" >/dev/null
openclaw models fallbacks clear >/dev/null
for fb in "${fallbacks[@]}"; do
openclaw models fallbacks add "$fb" >/dev/null
done
provider_policy_len="$(jq -r --arg p "$profile" '.profiles[$p].providerPolicy // {} | length' "$CONFIG_PATH")"
if [[ "$provider_policy_len" != "0" ]]; then
while IFS=$'\t' read -r provider enabled; do
[[ -z "$provider" || -z "$enabled" ]] && continue
openclaw config set --json "providers.$provider.enabled" "$enabled" >/dev/null || true
done < <(jq -r --arg p "$profile" '.profiles[$p].providerPolicy // {} | to_entries[] | "\(.key)\t\(.value)"' "$CONFIG_PATH")
fi
sync_policy_config "$primary" "${fallbacks[@]}"
if [[ "$apply_live" == "true" ]]; then
session_id="$(get_session_id "$SESSION_KEY")"
if [[ -n "$session_id" ]]; then
python3 - "$session_id" "$primary" <<'PY'
import subprocess
import sys
session_id = sys.argv[1]
primary = sys.argv[2]
cmd = [
"openclaw",
"agent",
"--session-id", session_id,
"--channel", "last",
"--message", f"/model {primary}",
]
try:
subprocess.run(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
timeout=20,
)
except subprocess.TimeoutExpired:
pass
PY
echo "[model-switch] live session updated: $session_id"
else
echo "[model-switch] no active session found for key $SESSION_KEY"
fi
fi
if [[ "$show_status" == "true" ]]; then
echo ""
openclaw models status
fi

View File

@ -1,118 +0,0 @@
#!/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"

View File

@ -1,201 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Optional overrides:
# OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot bash ./setup/setup_openclaw_copilot.sh
# NPM_GLOBAL_PREFIX="$HOME/.npm-global" bash ./setup/setup_openclaw_copilot.sh
# OPENCLAW_DATA_TARGET=/Volumes/Data/openclaw-copilot NPM_GLOBAL_PREFIX="$HOME/.npm-global" bash ./setup/setup_openclaw_copilot.sh
OPENCLAW_DATA_TARGET="${OPENCLAW_DATA_TARGET:-}"
NPM_GLOBAL_PREFIX="${NPM_GLOBAL_PREFIX:-}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { echo -e "${GREEN}$*${NC}"; }
log_warn() { echo -e "${YELLOW}$*${NC}"; }
log_err() { echo -e "${RED}$*${NC}"; }
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
log_err "ERROR: Missing required command: $cmd"
exit 1
fi
}
node_major_version() {
node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1
}
expand_tilde() {
local p="$1"
if [[ "$p" == "~" ]]; then
echo "$HOME"
elif [[ "$p" == "~/"* ]]; then
echo "$HOME/${p#~/}"
else
echo "$p"
fi
}
link_data_dir() {
local source="$1"
local target="$2"
local label="$3"
mkdir -p "$target"
if [ -L "$source" ]; then
local current_link
current_link="$(readlink "$source")"
if [ "$current_link" != "$target" ]; then
rm "$source"
ln -s "$target" "$source"
log_info "${label}: updated symlink ${source} -> ${target}"
else
log_info "${label}: symlink already correct (${source} -> ${target})"
fi
return 0
fi
if [ -d "$source" ]; then
log_warn "${label}: migrating existing ${source} data to ${target}..."
if command -v rsync >/dev/null 2>&1; then
rsync -a "$source"/ "$target"/
else
cp -a "$source"/. "$target"/
fi
rm -rf "$source"
elif [ -e "$source" ]; then
log_err "ERROR: ${source} exists but is not a directory/symlink. Resolve manually."
exit 1
fi
ln -s "$target" "$source"
log_info "${label}: symlink created ${source} -> ${target}"
}
install_npm_global() {
local pkg="$1"
require_cmd npm
if [[ -n "$NPM_GLOBAL_PREFIX" ]]; then
mkdir -p "$NPM_GLOBAL_PREFIX"
npm install -g --prefix "$NPM_GLOBAL_PREFIX" "$pkg"
else
npm install -g "$pkg"
fi
}
if [[ -n "$OPENCLAW_DATA_TARGET" ]]; then
OPENCLAW_DATA_TARGET="$(expand_tilde "$OPENCLAW_DATA_TARGET")"
fi
if [[ -n "$NPM_GLOBAL_PREFIX" ]]; then
NPM_GLOBAL_PREFIX="$(expand_tilde "$NPM_GLOBAL_PREFIX")"
fi
if [[ -n "$NPM_GLOBAL_PREFIX" ]]; then
export PATH="$NPM_GLOBAL_PREFIX/bin:$PATH"
fi
echo -e "${GREEN}=== OpenClaw + GitHub Copilot CLI Setup ===${NC}"
echo "Current time: $(date)"
if [[ -n "$OPENCLAW_DATA_TARGET" ]]; then
echo "OpenClaw data target: $OPENCLAW_DATA_TARGET"
fi
if [[ -n "$NPM_GLOBAL_PREFIX" ]]; then
echo "npm global prefix: $NPM_GLOBAL_PREFIX"
fi
echo ""
require_cmd curl
# Step 1: Install / verify Node.js >= 22
need_node_install=false
if ! command -v node >/dev/null 2>&1; then
need_node_install=true
else
node_major="$(node_major_version || true)"
if ! echo "$node_major" | grep -Eq '^[0-9]+$' || [ "$node_major" -lt 22 ]; then
need_node_install=true
fi
fi
if [ "$need_node_install" = true ]; then
log_warn "Node.js >= 22 not found. Installing via Homebrew..."
if ! command -v brew >/dev/null 2>&1; then
log_err "Homebrew not found. Install it first:"
echo '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
exit 1
fi
brew install node
fi
log_info "Node.js ready ($(node -v))."
# Step 2: Install / verify OpenClaw
if ! command -v openclaw >/dev/null 2>&1; then
log_warn "OpenClaw not found. Installing via npm..."
require_cmd npm
install_npm_global openclaw
else
log_info "OpenClaw already installed ($(openclaw --version))."
fi
# Step 3: Install / verify GitHub Copilot CLI
if ! command -v copilot >/dev/null 2>&1; then
if command -v brew >/dev/null 2>&1; then
log_warn "Copilot CLI not found. Installing prerelease via Homebrew..."
brew install copilot-cli@prerelease || {
log_warn "Homebrew install failed, falling back to npm package..."
install_npm_global @github/copilot-cli
}
else
log_warn "Homebrew not found. Installing Copilot CLI via npm..."
install_npm_global @github/copilot-cli
fi
else
log_info "Copilot CLI already installed ($(copilot --version 2>/dev/null || echo present))."
fi
if [[ -n "$OPENCLAW_DATA_TARGET" ]]; then
link_data_dir "$HOME/.openclaw" "$OPENCLAW_DATA_TARGET" "OpenClaw"
else
log_info "OpenClaw data location unchanged (~/.openclaw). Set OPENCLAW_DATA_TARGET to move it."
fi
FINALIZE_SCRIPT="$SCRIPT_DIR/../scripts/finalize_copilot_setup.sh"
if [[ -x "$FINALIZE_SCRIPT" ]]; then
if copilot auth status >/dev/null 2>&1; then
log_info "Copilot auth already active. Running finalize flow now..."
if ! bash "$FINALIZE_SCRIPT"; then
log_warn "Finalize flow failed. Re-run manually after checking auth/models:"
echo " bash ./scripts/finalize_copilot_setup.sh"
fi
else
log_warn "Final setup step still needed after login:"
echo " bash ./scripts/finalize_copilot_setup.sh"
fi
fi
echo ""
log_info "Setup complete (Copilot-first)."
echo ""
echo "Next steps (target machine):"
echo "1. Run one-command finalize (recommended):"
echo " bash ./scripts/finalize_copilot_setup.sh"
echo "2. If finalize cannot open browser login, authenticate first:"
echo " copilot auth login"
echo " copilot auth status"
echo " bash ./scripts/finalize_copilot_setup.sh"
echo "3. Verify:"
echo " openclaw status --deep"
echo " openclaw models status"
if [[ -n "$NPM_GLOBAL_PREFIX" ]]; then
echo ""
echo "If this is a new shell, ensure PATH includes your npm prefix bin:"
echo " export PATH=\"$NPM_GLOBAL_PREFIX/bin:\$PATH\""
fi