diff --git a/skills/claw-setup/README.md b/skills/claw-setup/README.md index f58ed22..4f29489 100644 --- a/skills/claw-setup/README.md +++ b/skills/claw-setup/README.md @@ -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 diff --git a/skills/claw-setup/SKILL.md b/skills/claw-setup/SKILL.md index 31f5ff5..39a5c08 100644 --- a/skills/claw-setup/SKILL.md +++ b/skills/claw-setup/SKILL.md @@ -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 diff --git a/skills/claw-setup/scripts/import-qwen-oauth.sh b/skills/claw-setup/scripts/import-qwen-oauth.sh index 1045843..beab4a8 100755 --- a/skills/claw-setup/scripts/import-qwen-oauth.sh +++ b/skills/claw-setup/scripts/import-qwen-oauth.sh @@ -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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/skills/claw-setup/scripts/qwen-token-refresh.sh b/skills/claw-setup/scripts/qwen-token-refresh.sh new file mode 100644 index 0000000..cf45fa1 --- /dev/null +++ b/skills/claw-setup/scripts/qwen-token-refresh.sh @@ -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