feat: add auto token refresh for ALL platforms

New qwen-token-refresh.sh script provides automatic token refresh
for OpenClaw, NanoBot, PicoClaw, NanoClaw (ZeroClaw has native support).

Features:
- Check token status and expiry
- Auto-refresh when < 5 min remaining
- Background daemon mode (5 min intervals)
- Systemd service installation
- Updates both oauth_creds.json and .env file

Usage:
  ./scripts/qwen-token-refresh.sh --status   # Check status
  ./scripts/qwen-token-refresh.sh            # Refresh if needed
  ./scripts/qwen-token-refresh.sh --daemon   # Background daemon
  ./scripts/qwen-token-refresh.sh --install  # Systemd service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-02-22 05:18:50 -05:00
Unverified
parent e21168c6ec
commit 9b46db629e
4 changed files with 421 additions and 11 deletions

View File

@@ -233,14 +233,14 @@ zeroclaw gateway
│ • Treats Qwen API as OpenAI-compatible endpoint │
│ • Extract access_token and use as OPENAI_API_KEY │
│ • Set OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 │
│ • Manual re-export needed when token expires
│ • Use qwen-token-refresh.sh for automatic refresh
│ │
│ COMPARISON: │
│ ┌─────────────────┬────────────────┬─────────────────────┐ │
│ │ Feature │ Native │ OpenAI-Compatible │ │
│ ├─────────────────┼────────────────┼─────────────────────┤ │
│ │ Token Refresh │ ✅ Automatic │ ❌ Manual │ │
│ │ Token Expiry │ ✅ Handled │ ⚠️ Re-export needed│ │
│ │ Token Refresh │ ✅ Automatic │ ✅ Script/Daemon │ │
│ │ Token Expiry │ ✅ Handled │ ✅ Handled │ │
│ │ Platforms │ ZeroClaw only │ All others │ │
│ │ Config File │ ~/.qwen/oauth_creds.json │ env vars │ │
│ └─────────────────┴────────────────┴─────────────────────┘ │
@@ -268,6 +268,29 @@ Qwen Code stores OAuth credentials in `~/.qwen/oauth_creds.json`:
| `refresh_token` | Used to get new access_token when expired |
| `expiry_date` | Unix timestamp when access_token expires |
### Auto Token Refresh for ALL Platforms
```bash
# Check token status
./scripts/qwen-token-refresh.sh --status
# Refresh if expired (5 min buffer)
./scripts/qwen-token-refresh.sh
# Run as background daemon
./scripts/qwen-token-refresh.sh --daemon
# Install as systemd service (auto-start)
./scripts/qwen-token-refresh.sh --install
systemctl --user enable --now qwen-token-refresh
```
The refresh script:
- Checks token expiry every 5 minutes
- Refreshes automatically when < 5 min remaining
- Updates `~/.qwen/oauth_creds.json` and `~/.qwen/.env`
- Works for ALL platforms (OpenClaw, NanoBot, PicoClaw, NanoClaw)
### API Endpoints
| Endpoint | URL |
@@ -575,11 +598,12 @@ GOOGLE_CLOUD_LOCATION=us-central1
```
skills/claw-setup/
├── SKILL.md # Skill definition (this file's source)
├── README.md # This documentation
├── SKILL.md # Skill definition (this file's source)
├── README.md # This documentation
└── scripts/
├── import-qwen-oauth.sh # Import FREE Qwen OAuth to any platform
── fetch-models.sh # Fetch models from all providers
├── import-qwen-oauth.sh # Import FREE Qwen OAuth to any platform
── qwen-token-refresh.sh # Auto-refresh tokens (daemon/systemd)
└── fetch-models.sh # Fetch models from all providers
```
---
@@ -598,11 +622,16 @@ find ~/.qwen -name "*.json"
### Token Expired
```bash
# Tokens auto-refresh in Qwen Code
qwen -p "refresh"
# Option 1: Use auto-refresh script
./scripts/qwen-token-refresh.sh
# Re-export
# Option 2: Manual re-auth
qwen --auth-type qwen-oauth -p "test"
source ~/.qwen/.env
# Option 3: Install systemd service for auto-refresh
./scripts/qwen-token-refresh.sh --install
systemctl --user enable --now qwen-token-refresh
```
### API Errors

View File

@@ -21,7 +21,7 @@ End-to-end professional setup of AI Agent platforms with **25+ OpenCode-compatib
│ • Works with: OpenClaw, NanoBot, PicoClaw, ZeroClaw │
│ • Model: coder-model (qwen3-coder-plus) │
│ • Auth: Browser OAuth via qwen.ai │
│ • Token refresh: Automatic (ZeroClaw) / Manual (others)
│ • Token refresh: Automatic (ALL platforms)
│ • DEFAULT: Recommended as primary provider │
│ │
│ FEATURE 2: 25+ OpenCode-Compatible AI Providers │
@@ -145,6 +145,46 @@ picoclaw # PicoClaw with FREE Qwen
nanoclaw # NanoClaw with FREE Qwen
```
## Auto Token Refresh (ALL Platforms)
Use the included refresh script to automatically refresh expired tokens:
```bash
# Check token status
./scripts/qwen-token-refresh.sh --status
# Refresh if expired
./scripts/qwen-token-refresh.sh
# Run as background daemon (checks every 5 min)
./scripts/qwen-token-refresh.sh --daemon
# Install as systemd service (auto-start on boot)
./scripts/qwen-token-refresh.sh --install
systemctl --user enable --now qwen-token-refresh
```
### How Auto-Refresh Works
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTO TOKEN REFRESH FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Check expiry_date in ~/.qwen/oauth_creds.json │
│ 2. If expired (< 5 min buffer): │
│ POST https://chat.qwen.ai/api/v1/oauth2/token │
│ Body: grant_type=refresh_token&refresh_token=xxx │
│ 3. Response: { access_token, refresh_token, expires_in } │
│ 4. Update ~/.qwen/oauth_creds.json with new tokens │
│ 5. Update ~/.qwen/.env with new OPENAI_API_KEY │
│ 6. Platforms using source ~/.qwen/.env get fresh token │
│ │
│ Systemd service: Runs every 5 minutes, refreshes when needed │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
# FEATURE 2: 25+ OpenCode-Compatible AI Providers

View File

@@ -199,4 +199,10 @@ echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "FREE Qwen OAuth: 2,000 requests/day, 60 req/min"
echo "API Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1"
echo ""
echo "🔄 AUTO TOKEN REFRESH (for non-ZeroClaw platforms):"
echo " ./scripts/qwen-token-refresh.sh --status # Check status"
echo " ./scripts/qwen-token-refresh.sh # Refresh if needed"
echo " ./scripts/qwen-token-refresh.sh --daemon # Background daemon"
echo " ./scripts/qwen-token-refresh.sh --install # Systemd service"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -0,0 +1,335 @@
#!/bin/bash
# qwen-token-refresh.sh - Auto-refresh Qwen OAuth token
# Usage:
# ./qwen-token-refresh.sh # Check and refresh if expired
# ./qwen-token-refresh.sh --daemon # Run as background daemon
# ./qwen-token-refresh.sh --status # Check token status
#
# This enables auto token refresh for ALL platforms:
# OpenClaw, NanoBot, PicoClaw, NanoClaw
#
# For ZeroClaw, this is NOT needed (native support).
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
CREDS_FILE="$HOME/.qwen/oauth_creds.json"
ENV_FILE="$HOME/.qwen/.env"
REFRESH_URL="https://chat.qwen.ai/api/v1/oauth2/token"
API_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
# Token refresh buffer (refresh 5 minutes before expiry)
REFRESH_BUFFER_SECONDS=300
check_dependencies() {
if ! command -v jq &> /dev/null; then
echo -e "${RED}Error: jq is required${NC}"
echo "Install with: sudo apt install jq"
exit 1
fi
if ! command -v curl &> /dev/null; then
echo -e "${RED}Error: curl is required${NC}"
exit 1
fi
}
get_token_status() {
if [ ! -f "$CREDS_FILE" ]; then
echo "NOT_FOUND"
return
fi
local expiry_date=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
local current_time=$(date +%s)000
if [ "$expiry_date" -eq 0 ]; then
echo "UNKNOWN"
return
fi
local expiry_seconds=$((expiry_date / 1000))
local current_seconds=$((current_time / 1000))
local remaining=$((expiry_seconds - current_seconds))
if [ "$remaining" -lt 0 ]; then
echo "EXPIRED"
elif [ "$remaining" -lt "$REFRESH_BUFFER_SECONDS" ]; then
echo "EXPIRING_SOON"
else
echo "VALID"
fi
}
format_time_remaining() {
local expiry_date=$1
local expiry_seconds=$((expiry_date / 1000))
local current_seconds=$(date +%s)
local remaining=$((expiry_seconds - current_seconds))
if [ "$remaining" -lt 0 ]; then
echo "expired"
elif [ "$remaining" -lt 60 ]; then
echo "${remaining}s"
elif [ "$remaining" -lt 3600 ]; then
echo "$((remaining / 60))m"
else
echo "$((remaining / 3600))h $(((remaining % 3600) / 60))m"
fi
}
refresh_token() {
local refresh_token=$(cat "$CREDS_FILE" | jq -r '.refresh_token // empty')
if [ -z "$refresh_token" ] || [ "$refresh_token" = "null" ]; then
echo -e "${RED}Error: No refresh_token found in credentials${NC}"
echo "Please re-authenticate: qwen --auth-type qwen-oauth -p 'test'"
return 1
fi
echo -e "${BLUE}Refreshing token...${NC}"
local response=$(curl -s -X POST "$REFRESH_URL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$refresh_token" \
2>/dev/null)
if [ -z "$response" ]; then
echo -e "${RED}Error: Empty response from refresh endpoint${NC}"
return 1
fi
local new_access_token=$(echo "$response" | jq -r '.access_token // empty')
local new_refresh_token=$(echo "$response" | jq -r '.refresh_token // empty')
local expires_in=$(echo "$response" | jq -r '.expires_in // 3600')
if [ -z "$new_access_token" ] || [ "$new_access_token" = "null" ]; then
echo -e "${RED}Error: Failed to get new access_token${NC}"
echo "Response: $response"
echo ""
echo "Please re-authenticate: qwen --auth-type qwen-oauth -p 'test'"
return 1
fi
# Calculate new expiry date (current time + expires_in, in milliseconds)
local current_ms=$(date +%s)000
local expires_in_ms=$((expires_in * 1000))
local new_expiry=$((current_ms + expires_in_ms))
# Update credentials file
local resource_url=$(cat "$CREDS_FILE" | jq -r '.resource_url // "portal.qwen.ai"')
cat > "$CREDS_FILE" << EOF
{
"access_token": "$new_access_token",
"refresh_token": "${new_refresh_token:-$refresh_token}",
"token_type": "Bearer",
"resource_url": "$resource_url",
"expiry_date": $new_expiry
}
EOF
chmod 600 "$CREDS_FILE"
# Update .env file for other platforms
mkdir -p "$HOME/.qwen"
cat > "$ENV_FILE" << EOF
# Qwen OAuth Configuration (auto-refreshed: $(date))
export OPENAI_API_KEY="$new_access_token"
export OPENAI_BASE_URL="$API_BASE_URL"
export OPENAI_MODEL="coder-model"
EOF
chmod 600 "$ENV_FILE"
echo -e "${GREEN}✅ Token refreshed successfully!${NC}"
echo " New token expires in: $(format_time_remaining $new_expiry)"
echo ""
echo "Environment updated. Run to apply:"
echo " source ~/.qwen/.env"
return 0
}
show_status() {
echo "🦞 Qwen OAuth Token Status"
echo "==========================="
echo ""
if [ ! -f "$CREDS_FILE" ]; then
echo -e "${RED}❌ Credentials file not found${NC}"
echo " Expected: $CREDS_FILE"
echo ""
echo "To authenticate, run:"
echo " qwen --auth-type qwen-oauth -p 'test'"
exit 1
fi
local status=$(get_token_status)
local expiry_date=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
local access_token=$(cat "$CREDS_FILE" | jq -r '.access_token // ""')
echo "Credentials: $CREDS_FILE"
echo "Token: ${access_token:0:20}...${access_token: -10}"
echo ""
case "$status" in
VALID)
echo -e "Status: ${GREEN}✅ VALID${NC}"
echo "Expires in: $(format_time_remaining $expiry_date)"
;;
EXPIRING_SOON)
echo -e "Status: ${YELLOW}⚠️ EXPIRING SOON${NC}"
echo "Expires in: $(format_time_remaining $expiry_date)"
echo ""
echo "Run refresh: $0"
;;
EXPIRED)
echo -e "Status: ${RED}❌ EXPIRED${NC}"
echo ""
echo "Run refresh: $0"
;;
UNKNOWN)
echo -e "Status: ${YELLOW}⚠️ UNKNOWN${NC}"
echo "Expiry date not found in credentials"
;;
esac
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "FREE tier: 2,000 requests/day, 60 req/min"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
run_daemon() {
echo "🦞 Qwen Token Refresh Daemon"
echo "============================="
echo ""
echo "Starting background refresh service..."
echo "Check interval: 5 minutes"
echo "Refresh buffer: ${REFRESH_BUFFER_SECONDS}s before expiry"
echo ""
echo "Press Ctrl+C to stop"
echo ""
while true; do
local status=$(get_token_status)
case "$status" in
EXPIRED|EXPIRING_SOON)
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token needs refresh ($status)"
if refresh_token > /dev/null 2>&1; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - ✅ Token refreshed"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') - ❌ Refresh failed"
fi
;;
VALID)
local expiry=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token valid ($(format_time_remaining $expiry) remaining)"
;;
*)
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token status: $status"
;;
esac
sleep 300 # Check every 5 minutes
done
}
install_systemd_service() {
local service_dir="$HOME/.config/systemd/user"
local service_file="$service_dir/qwen-token-refresh.service"
mkdir -p "$service_dir"
cat > "$service_file" << 'EOF'
[Unit]
Description=Qwen OAuth Token Auto-Refresh
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/qwen-token-refresh.sh --daemon
Restart=always
RestartSec=60
[Install]
WantedBy=default.target
EOF
echo -e "${GREEN}✅ Systemd service installed${NC}"
echo ""
echo "To enable and start:"
echo " systemctl --user daemon-reload"
echo " systemctl --user enable qwen-token-refresh"
echo " systemctl --user start qwen-token-refresh"
echo ""
echo "To check status:"
echo " systemctl --user status qwen-token-refresh"
}
# Main
check_dependencies
case "${1:-}" in
--status|-s)
show_status
;;
--daemon|-d)
run_daemon
;;
--install|-i)
install_systemd_service
;;
--help|-h)
echo "Usage: $0 [OPTION]"
echo ""
echo "Options:"
echo " (none) Check and refresh token if expired"
echo " --status Show token status"
echo " --daemon Run as background refresh daemon"
echo " --install Install as systemd user service"
echo " --help Show this help"
echo ""
echo "Examples:"
echo " $0 # Refresh if needed"
echo " $0 --status # Check status"
echo " $0 --daemon # Run background daemon"
echo " $0 --install # Install systemd service"
;;
*)
# Default: check and refresh if needed
if [ ! -f "$CREDS_FILE" ]; then
echo -e "${RED}Error: Credentials not found${NC}"
echo "Run: qwen --auth-type qwen-oauth -p 'test'"
exit 1
fi
local status=$(get_token_status)
case "$status" in
VALID)
local expiry=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
echo -e "${GREEN}✅ Token is valid${NC} ($(format_time_remaining $expiry) remaining)"
echo ""
echo "To force refresh anyway, delete ~/.qwen/oauth_creds.json and re-auth"
;;
EXPIRED|EXPIRING_SOON)
echo -e "${YELLOW}Token needs refresh ($status)${NC}"
refresh_token
;;
*)
echo -e "${YELLOW}Unknown token status, attempting refresh...${NC}"
refresh_token
;;
esac
;;
esac