mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-04-05 18:50:37 +08:00
855 lines
30 KiB
Bash
Executable File
855 lines
30 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# ============================
|
|
# CowAgent Management Script
|
|
# ============================
|
|
|
|
# ANSI colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
# Emojis
|
|
EMOJI_ROCKET="🚀"
|
|
EMOJI_COW="🐄"
|
|
EMOJI_CHECK="✅"
|
|
EMOJI_CROSS="❌"
|
|
EMOJI_WARN="⚠️"
|
|
EMOJI_STOP="🛑"
|
|
EMOJI_WRENCH="🔧"
|
|
|
|
# Check if using Bash
|
|
if [ -z "$BASH_VERSION" ]; then
|
|
echo -e "${RED}❌ Please run this script with Bash.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Get current script directory
|
|
export BASE_DIR=$(cd "$(dirname "$0")"; pwd)
|
|
|
|
# Detect if in project directory
|
|
IS_PROJECT_DIR=false
|
|
if [ -f "${BASE_DIR}/config-template.json" ] && [ -f "${BASE_DIR}/app.py" ]; then
|
|
IS_PROJECT_DIR=true
|
|
fi
|
|
|
|
# Check and install tool
|
|
check_and_install_tool() {
|
|
local tool_name=$1
|
|
if ! command -v "$tool_name" &> /dev/null; then
|
|
echo -e "${YELLOW}⚙️ $tool_name not found, installing...${NC}"
|
|
if command -v yum &> /dev/null; then
|
|
sudo yum install "$tool_name" -y
|
|
elif command -v apt-get &> /dev/null; then
|
|
sudo apt-get update && sudo apt-get install "$tool_name" -y
|
|
elif command -v brew &> /dev/null; then
|
|
brew install "$tool_name"
|
|
else
|
|
echo -e "${RED}❌ Unsupported package manager. Please install $tool_name manually.${NC}"
|
|
return 1
|
|
fi
|
|
|
|
if ! command -v "$tool_name" &> /dev/null; then
|
|
echo -e "${RED}❌ Failed to install $tool_name.${NC}"
|
|
return 1
|
|
else
|
|
echo -e "${GREEN}✅ $tool_name installed successfully.${NC}"
|
|
return 0
|
|
fi
|
|
else
|
|
echo -e "${GREEN}✅ $tool_name is already installed.${NC}"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Detect and set Python command
|
|
detect_python_command() {
|
|
FOUND_NEWER_VERSION=""
|
|
|
|
# Try to find Python command in order of preference
|
|
for cmd in python3 python python3.12 python3.11 python3.10 python3.9 python3.8 python3.7; do
|
|
if command -v $cmd &> /dev/null; then
|
|
# Check Python version
|
|
major_version=$($cmd -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
|
|
minor_version=$($cmd -c 'import sys; print(sys.version_info[1])' 2>/dev/null)
|
|
|
|
if [[ "$major_version" == "3" ]]; then
|
|
# Check if version is in supported range (3.7 - 3.12)
|
|
if (( minor_version >= 7 && minor_version <= 12 )); then
|
|
PYTHON_CMD=$cmd
|
|
PYTHON_VERSION="${major_version}.${minor_version}"
|
|
break
|
|
elif (( minor_version >= 13 )); then
|
|
# Found Python 3.13+, but not compatible
|
|
if [ -z "$FOUND_NEWER_VERSION" ]; then
|
|
FOUND_NEWER_VERSION="${major_version}.${minor_version}"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ -z "$PYTHON_CMD" ]; then
|
|
echo -e "${YELLOW}Tried: python3, python, python3.12, python3.11, python3.10, python3.9, python3.8, python3.7${NC}"
|
|
if [ -n "$FOUND_NEWER_VERSION" ]; then
|
|
echo -e "${RED}❌ Found Python $FOUND_NEWER_VERSION, but this project requires Python 3.7-3.12${NC}"
|
|
echo -e "${YELLOW}Python 3.13+ has compatibility issues with some dependencies (web.py, cgi module removed)${NC}"
|
|
echo -e "${YELLOW}Please install Python 3.7-3.12 (recommend Python 3.12)${NC}"
|
|
else
|
|
echo -e "${RED}❌ No suitable Python found. Please install Python 3.7-3.12${NC}"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Export for global use
|
|
export PYTHON_CMD
|
|
export PYTHON_VERSION
|
|
|
|
echo -e "${GREEN}✅ Found Python: $PYTHON_CMD (version $PYTHON_VERSION)${NC}"
|
|
}
|
|
|
|
# Check Python version (>= 3.7)
|
|
check_python_version() {
|
|
detect_python_command
|
|
|
|
# Verify pip is available
|
|
if ! $PYTHON_CMD -m pip --version &> /dev/null; then
|
|
echo -e "${RED}❌ pip not found for $PYTHON_CMD. Please install pip.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ pip is available for $PYTHON_CMD${NC}"
|
|
}
|
|
|
|
# Clone project
|
|
clone_project() {
|
|
echo -e "${GREEN}🔍 Cloning ChatGPT-on-WeChat project...${NC}"
|
|
|
|
if [ -d "chatgpt-on-wechat" ]; then
|
|
echo -e "${YELLOW}⚠️ Directory 'chatgpt-on-wechat' already exists.${NC}"
|
|
read -p "Choose action: overwrite(o), backup(b), or quit(q)? [press Enter for default: b]: " choice
|
|
choice=${choice:-b}
|
|
case "$choice" in
|
|
o|O)
|
|
echo -e "${YELLOW}🗑️ Overwriting 'chatgpt-on-wechat' directory...${NC}"
|
|
rm -rf chatgpt-on-wechat
|
|
;;
|
|
b|B)
|
|
backup_dir="chatgpt-on-wechat_backup_$(date +%s)"
|
|
echo -e "${YELLOW}🔀 Backing up to '$backup_dir'...${NC}"
|
|
mv chatgpt-on-wechat "$backup_dir"
|
|
;;
|
|
q|Q)
|
|
echo -e "${RED}❌ Installation cancelled.${NC}"
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo -e "${RED}❌ Invalid choice. Exiting.${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
check_and_install_tool git
|
|
|
|
if ! command -v git &> /dev/null; then
|
|
echo -e "${YELLOW}⚠️ Git not available. Trying wget/curl...${NC}"
|
|
local zip_url="https://gitee.com/zhayujie/chatgpt-on-wechat/repository/archive/master.zip"
|
|
if command -v wget &> /dev/null; then
|
|
wget "$zip_url" -O chatgpt-on-wechat.zip
|
|
elif command -v curl &> /dev/null; then
|
|
curl -L "$zip_url" -o chatgpt-on-wechat.zip
|
|
else
|
|
echo -e "${RED}❌ Cannot download project. Please install Git, wget, or curl.${NC}"
|
|
exit 1
|
|
fi
|
|
unzip chatgpt-on-wechat.zip
|
|
mv chatgpt-on-wechat-master chatgpt-on-wechat
|
|
rm chatgpt-on-wechat.zip
|
|
else
|
|
git clone https://github.com/zhayujie/chatgpt-on-wechat.git || \
|
|
git clone https://gitee.com/zhayujie/chatgpt-on-wechat.git
|
|
if [[ $? -ne 0 ]]; then
|
|
echo -e "${RED}❌ Project clone failed. Please check network connection.${NC}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
cd chatgpt-on-wechat || { echo -e "${RED}❌ Failed to enter project directory.${NC}"; exit 1; }
|
|
export BASE_DIR=$(pwd)
|
|
echo -e "${GREEN}✅ Project cloned successfully: $BASE_DIR${NC}"
|
|
|
|
# Add execute permission to management script
|
|
if [ -f "${BASE_DIR}/run.sh" ]; then
|
|
chmod +x "${BASE_DIR}/run.sh" 2>/dev/null || true
|
|
echo -e "${GREEN}✅ Execute permission added to run.sh${NC}"
|
|
fi
|
|
|
|
sleep 1
|
|
}
|
|
|
|
# Install dependencies
|
|
install_dependencies() {
|
|
echo -e "${GREEN}📦 Installing dependencies...${NC}"
|
|
local PIP_MIRROR="-i https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
|
|
PIP_EXTRA_ARGS=""
|
|
if $PYTHON_CMD -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)" 2>/dev/null; then
|
|
PIP_EXTRA_ARGS="--break-system-packages"
|
|
echo -e "${YELLOW}Python 3.11+ detected, using --break-system-packages for pip installations${NC}"
|
|
fi
|
|
|
|
echo -e "${YELLOW}Upgrading pip and basic tools...${NC}"
|
|
set +e
|
|
$PYTHON_CMD -m pip install --upgrade pip setuptools wheel importlib_metadata --ignore-installed $PIP_EXTRA_ARGS $PIP_MIRROR > /tmp/pip_upgrade.log 2>&1
|
|
[ $? -ne 0 ] && echo -e "${YELLOW}⚠️ Some tools failed to upgrade, but continuing...${NC}"
|
|
set -e
|
|
rm -f /tmp/pip_upgrade.log
|
|
|
|
echo -e "${YELLOW}Installing project dependencies...${NC}"
|
|
set +e
|
|
$PYTHON_CMD -m pip install -r requirements.txt $PIP_EXTRA_ARGS $PIP_MIRROR > /tmp/pip_install.log 2>&1
|
|
local exit_code=$?
|
|
set -e
|
|
cat /tmp/pip_install.log
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
echo -e "${GREEN}✅ Dependencies installed successfully.${NC}"
|
|
elif grep -qE "distutils installed project|uninstall-no-record-file|installed by debian" /tmp/pip_install.log; then
|
|
echo -e "${YELLOW}⚠️ Detected system package conflict, retrying with workaround...${NC}"
|
|
local IGNORE_PACKAGES=""
|
|
for pkg in PyYAML setuptools wheel certifi charset-normalizer; do
|
|
IGNORE_PACKAGES="$IGNORE_PACKAGES --ignore-installed $pkg"
|
|
done
|
|
set +e
|
|
$PYTHON_CMD -m pip install -r requirements.txt $IGNORE_PACKAGES $PIP_EXTRA_ARGS $PIP_MIRROR \
|
|
&& echo -e "${GREEN}✅ Dependencies installed successfully (workaround applied).${NC}" \
|
|
|| echo -e "${YELLOW}⚠️ Some dependencies may have issues, but continuing...${NC}"
|
|
set -e
|
|
elif grep -q "externally-managed-environment" /tmp/pip_install.log; then
|
|
echo -e "${YELLOW}⚠️ Detected externally-managed environment, retrying with --break-system-packages...${NC}"
|
|
set +e
|
|
$PYTHON_CMD -m pip install -r requirements.txt --break-system-packages $PIP_MIRROR \
|
|
&& echo -e "${GREEN}✅ Dependencies installed successfully (system packages override applied).${NC}" \
|
|
|| echo -e "${YELLOW}⚠️ Some dependencies may have issues, but continuing...${NC}"
|
|
set -e
|
|
else
|
|
echo -e "${YELLOW}⚠️ Installation had errors, but continuing...${NC}"
|
|
fi
|
|
|
|
rm -f /tmp/pip_install.log
|
|
}
|
|
|
|
# Select model
|
|
select_model() {
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${CYAN}${BOLD} Select AI Model${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${YELLOW}1) MiniMax (MiniMax-M2.7, MiniMax-M2.5, etc.)${NC}"
|
|
echo -e "${YELLOW}2) Zhipu AI (glm-5-turbo, glm-5, etc.)${NC}"
|
|
echo -e "${YELLOW}3) Kimi (kimi-k2.5, kimi-k2, etc.)${NC}"
|
|
echo -e "${YELLOW}4) Doubao (doubao-seed-2-0-code-preview-260215, etc.)${NC}"
|
|
echo -e "${YELLOW}5) Qwen (qwen3.5-plus, qwen3-max, qwq-plus, etc.)${NC}"
|
|
echo -e "${YELLOW}6) Claude (claude-sonnet-4-6, claude-opus-4-6, etc.)${NC}"
|
|
echo -e "${YELLOW}7) Gemini (gemini-3.1-flash-lite-preview, gemini-3.1-pro-preview, etc.)${NC}"
|
|
echo -e "${YELLOW}8) OpenAI GPT (gpt-5.4, gpt-5.2, gpt-4.1, etc.)${NC}"
|
|
echo -e "${YELLOW}9) LinkAI (access multiple models via one API)${NC}"
|
|
echo ""
|
|
|
|
while true; do
|
|
read -p "Enter your choice [press Enter for default: 1 - MiniMax]: " model_choice
|
|
model_choice=${model_choice:-1}
|
|
case "$model_choice" in
|
|
1|2|3|4|5|6|7|8|9)
|
|
break
|
|
;;
|
|
*)
|
|
echo -e "${RED}Invalid choice. Please enter 1-9.${NC}"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Read model config: provider, default_model, key_variable_name
|
|
read_model_config() {
|
|
local provider=$1 default_model=$2 key_var=$3
|
|
echo -e "${GREEN}Configuring ${provider}...${NC}"
|
|
read -p "Enter ${provider} API Key: " _api_key
|
|
read -p "Enter model name [press Enter for default: ${default_model}]: " model_name
|
|
model_name=${model_name:-$default_model}
|
|
MODEL_NAME="$model_name"
|
|
eval "${key_var}=\"\$_api_key\""
|
|
}
|
|
|
|
# Read optional API base URL
|
|
read_api_base() {
|
|
local base_var=$1 default_url=$2
|
|
read -p "Enter API Base URL [press Enter for default: ${default_url}]: " api_base
|
|
api_base=${api_base:-$default_url}
|
|
eval "${base_var}=\"\$api_base\""
|
|
}
|
|
|
|
# Configure model
|
|
configure_model() {
|
|
case "$model_choice" in
|
|
1) read_model_config "MiniMax" "MiniMax-M2.7" "MINIMAX_KEY" ;;
|
|
2) read_model_config "Zhipu AI" "glm-5-turbo" "ZHIPU_KEY" ;;
|
|
3) read_model_config "Kimi (Moonshot)" "kimi-k2.5" "MOONSHOT_KEY" ;;
|
|
4) read_model_config "Doubao (Volcengine Ark)" "doubao-seed-2-0-code-preview-260215" "ARK_KEY" ;;
|
|
5) read_model_config "Qwen (DashScope)" "qwen3.5-plus" "DASHSCOPE_KEY" ;;
|
|
6)
|
|
read_model_config "Claude" "claude-sonnet-4-6" "CLAUDE_KEY"
|
|
read_api_base "CLAUDE_BASE" "https://api.anthropic.com/v1"
|
|
;;
|
|
7)
|
|
read_model_config "Gemini" "gemini-3.1-pro-preview" "GEMINI_KEY"
|
|
read_api_base "GEMINI_BASE" "https://generativelanguage.googleapis.com"
|
|
;;
|
|
8)
|
|
read_model_config "OpenAI GPT" "gpt-5.4" "OPENAI_KEY"
|
|
read_api_base "OPENAI_BASE" "https://api.openai.com/v1"
|
|
;;
|
|
9)
|
|
read_model_config "LinkAI" "MiniMax-M2.7" "LINKAI_KEY"
|
|
USE_LINKAI="true"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Select channel
|
|
select_channel() {
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${CYAN}${BOLD} Select Communication Channel${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${YELLOW}1) Weixin (微信)${NC}"
|
|
echo -e "${YELLOW}2) Feishu (飞书)${NC}"
|
|
echo -e "${YELLOW}3) DingTalk (钉钉)${NC}"
|
|
echo -e "${YELLOW}4) WeCom Bot (企微智能机器人)${NC}"
|
|
echo -e "${YELLOW}5) QQ (QQ 机器人)${NC}"
|
|
echo -e "${YELLOW}6) WeCom App (企微自建应用)${NC}"
|
|
echo -e "${YELLOW}7) Web (网页)${NC}"
|
|
echo ""
|
|
|
|
while true; do
|
|
read -p "Enter your choice [press Enter for default: 1 - Weixin]: " channel_choice
|
|
channel_choice=${channel_choice:-1}
|
|
case "$channel_choice" in
|
|
1|2|3|4|5|6|7)
|
|
break
|
|
;;
|
|
*)
|
|
echo -e "${RED}Invalid choice. Please enter 1-7.${NC}"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Configure channel
|
|
configure_channel() {
|
|
case "$channel_choice" in
|
|
1)
|
|
# Weixin
|
|
CHANNEL_TYPE="weixin"
|
|
ACCESS_INFO="Weixin channel configured. Scan QR code in terminal or web console to login."
|
|
;;
|
|
2)
|
|
# Feishu (WebSocket mode)
|
|
CHANNEL_TYPE="feishu"
|
|
echo -e "${GREEN}Configure Feishu (WebSocket mode)...${NC}"
|
|
read -p "Enter Feishu App ID: " fs_app_id
|
|
read -p "Enter Feishu App Secret: " fs_app_secret
|
|
|
|
FEISHU_APP_ID="$fs_app_id"
|
|
FEISHU_APP_SECRET="$fs_app_secret"
|
|
FEISHU_EVENT_MODE="websocket"
|
|
ACCESS_INFO="Feishu channel configured (WebSocket mode)"
|
|
;;
|
|
3)
|
|
# DingTalk
|
|
CHANNEL_TYPE="dingtalk"
|
|
echo -e "${GREEN}Configure DingTalk...${NC}"
|
|
read -p "Enter DingTalk Client ID: " dt_client_id
|
|
read -p "Enter DingTalk Client Secret: " dt_client_secret
|
|
|
|
DT_CLIENT_ID="$dt_client_id"
|
|
DT_CLIENT_SECRET="$dt_client_secret"
|
|
ACCESS_INFO="DingTalk channel configured"
|
|
;;
|
|
4)
|
|
# WeCom Bot
|
|
CHANNEL_TYPE="wecom_bot"
|
|
echo -e "${GREEN}Configure WeCom Bot...${NC}"
|
|
read -p "Enter WeCom Bot ID: " wecom_bot_id
|
|
read -p "Enter WeCom Bot Secret: " wecom_bot_secret
|
|
|
|
WECOM_BOT_ID="$wecom_bot_id"
|
|
WECOM_BOT_SECRET="$wecom_bot_secret"
|
|
ACCESS_INFO="WeCom Bot channel configured"
|
|
;;
|
|
5)
|
|
# QQ
|
|
CHANNEL_TYPE="qq"
|
|
echo -e "${GREEN}Configure QQ Bot...${NC}"
|
|
read -p "Enter QQ App ID: " qq_app_id
|
|
read -p "Enter QQ App Secret: " qq_app_secret
|
|
|
|
QQ_APP_ID="$qq_app_id"
|
|
QQ_APP_SECRET="$qq_app_secret"
|
|
ACCESS_INFO="QQ Bot channel configured"
|
|
;;
|
|
6)
|
|
# WeCom App
|
|
CHANNEL_TYPE="wechatcom_app"
|
|
echo -e "${GREEN}Configure WeCom App...${NC}"
|
|
read -p "Enter WeChat Corp ID: " corp_id
|
|
read -p "Enter WeChat Com App Token: " com_token
|
|
read -p "Enter WeChat Com App Secret: " com_secret
|
|
read -p "Enter WeChat Com App Agent ID: " com_agent_id
|
|
read -p "Enter WeChat Com App AES Key: " com_aes_key
|
|
read -p "Enter WeChat Com App Port [press Enter for default: 9898]: " com_port
|
|
com_port=${com_port:-9898}
|
|
|
|
WECHATCOM_CORP_ID="$corp_id"
|
|
WECHATCOM_TOKEN="$com_token"
|
|
WECHATCOM_SECRET="$com_secret"
|
|
WECHATCOM_AGENT_ID="$com_agent_id"
|
|
WECHATCOM_AES_KEY="$com_aes_key"
|
|
WECHATCOM_PORT="$com_port"
|
|
ACCESS_INFO="WeCom App channel configured on port ${com_port}"
|
|
;;
|
|
7)
|
|
# Web
|
|
CHANNEL_TYPE="web"
|
|
read -p "Enter web port [press Enter for default: 9899]: " web_port
|
|
web_port=${web_port:-9899}
|
|
|
|
WEB_PORT="$web_port"
|
|
ACCESS_INFO="Web interface will be available at: http://localhost:${web_port}/chat"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Generate config file
|
|
create_config_file() {
|
|
echo -e "${GREEN}📝 Generating config.json...${NC}"
|
|
|
|
CHANNEL_TYPE="$CHANNEL_TYPE" \
|
|
MODEL_NAME="$MODEL_NAME" \
|
|
OPENAI_KEY="${OPENAI_KEY:-}" \
|
|
OPENAI_BASE="${OPENAI_BASE:-https://api.openai.com/v1}" \
|
|
CLAUDE_KEY="${CLAUDE_KEY:-}" \
|
|
CLAUDE_BASE="${CLAUDE_BASE:-https://api.anthropic.com/v1}" \
|
|
GEMINI_KEY="${GEMINI_KEY:-}" \
|
|
GEMINI_BASE="${GEMINI_BASE:-https://generativelanguage.googleapis.com}" \
|
|
ZHIPU_KEY="${ZHIPU_KEY:-}" \
|
|
MOONSHOT_KEY="${MOONSHOT_KEY:-}" \
|
|
ARK_KEY="${ARK_KEY:-}" \
|
|
DASHSCOPE_KEY="${DASHSCOPE_KEY:-}" \
|
|
MINIMAX_KEY="${MINIMAX_KEY:-}" \
|
|
USE_LINKAI="${USE_LINKAI:-false}" \
|
|
LINKAI_KEY="${LINKAI_KEY:-}" \
|
|
FEISHU_APP_ID="${FEISHU_APP_ID:-}" \
|
|
FEISHU_APP_SECRET="${FEISHU_APP_SECRET:-}" \
|
|
WEB_PORT="${WEB_PORT:-}" \
|
|
DT_CLIENT_ID="${DT_CLIENT_ID:-}" \
|
|
DT_CLIENT_SECRET="${DT_CLIENT_SECRET:-}" \
|
|
WECOM_BOT_ID="${WECOM_BOT_ID:-}" \
|
|
WECOM_BOT_SECRET="${WECOM_BOT_SECRET:-}" \
|
|
QQ_APP_ID="${QQ_APP_ID:-}" \
|
|
QQ_APP_SECRET="${QQ_APP_SECRET:-}" \
|
|
WECHATCOM_CORP_ID="${WECHATCOM_CORP_ID:-}" \
|
|
WECHATCOM_TOKEN="${WECHATCOM_TOKEN:-}" \
|
|
WECHATCOM_SECRET="${WECHATCOM_SECRET:-}" \
|
|
WECHATCOM_AGENT_ID="${WECHATCOM_AGENT_ID:-}" \
|
|
WECHATCOM_AES_KEY="${WECHATCOM_AES_KEY:-}" \
|
|
WECHATCOM_PORT="${WECHATCOM_PORT:-}" \
|
|
$PYTHON_CMD -c "
|
|
import json, os
|
|
e = os.environ.get
|
|
base = {
|
|
'channel_type': e('CHANNEL_TYPE'),
|
|
'model': e('MODEL_NAME'),
|
|
'open_ai_api_key': e('OPENAI_KEY', ''),
|
|
'open_ai_api_base': e('OPENAI_BASE'),
|
|
'claude_api_key': e('CLAUDE_KEY', ''),
|
|
'claude_api_base': e('CLAUDE_BASE'),
|
|
'gemini_api_key': e('GEMINI_KEY', ''),
|
|
'gemini_api_base': e('GEMINI_BASE'),
|
|
'zhipu_ai_api_key': e('ZHIPU_KEY', ''),
|
|
'moonshot_api_key': e('MOONSHOT_KEY', ''),
|
|
'ark_api_key': e('ARK_KEY', ''),
|
|
'dashscope_api_key': e('DASHSCOPE_KEY', ''),
|
|
'minimax_api_key': e('MINIMAX_KEY', ''),
|
|
'voice_to_text': 'openai',
|
|
'text_to_voice': 'openai',
|
|
'voice_reply_voice': False,
|
|
'speech_recognition': True,
|
|
'group_speech_recognition': False,
|
|
'use_linkai': e('USE_LINKAI') == 'true',
|
|
'linkai_api_key': e('LINKAI_KEY', ''),
|
|
'linkai_app_code': '',
|
|
'agent': True,
|
|
'agent_max_context_tokens': 40000,
|
|
'agent_max_context_turns': 30,
|
|
'agent_max_steps': 15,
|
|
}
|
|
channel_map = {
|
|
'feishu': {'feishu_app_id': 'FEISHU_APP_ID', 'feishu_app_secret': 'FEISHU_APP_SECRET'},
|
|
'web': {'web_port': ('WEB_PORT', int)},
|
|
'dingtalk': {'dingtalk_client_id': 'DT_CLIENT_ID', 'dingtalk_client_secret': 'DT_CLIENT_SECRET'},
|
|
'wecom_bot': {'wecom_bot_id': 'WECOM_BOT_ID', 'wecom_bot_secret': 'WECOM_BOT_SECRET'},
|
|
'qq': {'qq_app_id': 'QQ_APP_ID', 'qq_app_secret': 'QQ_APP_SECRET'},
|
|
'wechatcom_app': {'wechatcom_corp_id': 'WECHATCOM_CORP_ID', 'wechatcomapp_token': 'WECHATCOM_TOKEN', 'wechatcomapp_secret': 'WECHATCOM_SECRET', 'wechatcomapp_agent_id': 'WECHATCOM_AGENT_ID', 'wechatcomapp_aes_key': 'WECHATCOM_AES_KEY', 'wechatcomapp_port': ('WECHATCOM_PORT', int)},
|
|
}
|
|
ch = e('CHANNEL_TYPE')
|
|
for key, spec in channel_map.get(ch, {}).items():
|
|
if isinstance(spec, tuple):
|
|
env_name, conv = spec
|
|
base[key] = conv(e(env_name))
|
|
else:
|
|
base[key] = e(spec, '')
|
|
with open('config.json', 'w') as f:
|
|
json.dump(base, f, indent=2, ensure_ascii=False)
|
|
"
|
|
|
|
echo -e "${GREEN}✅ Configuration file created successfully.${NC}"
|
|
}
|
|
|
|
# Start project
|
|
start_project() {
|
|
echo ""
|
|
echo -e "${GREEN}${EMOJI_ROCKET} Starting CowAgent...${NC}"
|
|
sleep 1
|
|
|
|
if [ ! -f "${BASE_DIR}/nohup.out" ]; then
|
|
touch "${BASE_DIR}/nohup.out"
|
|
fi
|
|
|
|
OS_TYPE=$(uname)
|
|
|
|
if [[ "$OS_TYPE" == "Linux" ]]; then
|
|
# Linux: use setsid to detach from terminal
|
|
nohup setsid $PYTHON_CMD "${BASE_DIR}/app.py" > "${BASE_DIR}/nohup.out" 2>&1 &
|
|
echo -e "${GREEN}${EMOJI_COW} CowAgent started on Linux (using $PYTHON_CMD)${NC}"
|
|
elif [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
# macOS: use nohup to prevent SIGHUP
|
|
nohup $PYTHON_CMD "${BASE_DIR}/app.py" > "${BASE_DIR}/nohup.out" 2>&1 &
|
|
echo -e "${GREEN}${EMOJI_COW} CowAgent started on macOS (using $PYTHON_CMD)${NC}"
|
|
else
|
|
echo -e "${RED}❌ Unsupported OS: ${OS_TYPE}${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
sleep 2
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${GREEN}${EMOJI_CHECK} CowAgent is now running in background!${NC}"
|
|
echo -e "${GREEN}${EMOJI_CHECK} Process will continue after closing terminal.${NC}"
|
|
echo -e "${CYAN}$ACCESS_INFO${NC}"
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}Management Commands:${NC}"
|
|
echo -e " ${GREEN}./run.sh stop${NC} Stop the service"
|
|
echo -e " ${GREEN}./run.sh restart${NC} Restart the service"
|
|
echo -e " ${GREEN}./run.sh status${NC} Check status"
|
|
echo -e " ${GREEN}./run.sh logs${NC} View logs"
|
|
echo -e " ${GREEN}./run.sh update${NC} Update and restart"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo ""
|
|
|
|
echo -e "${YELLOW}Showing recent logs (Ctrl+C to exit, agent keeps running):${NC}"
|
|
sleep 2
|
|
tail -n 30 -f "${BASE_DIR}/nohup.out"
|
|
}
|
|
|
|
# Show usage
|
|
show_usage() {
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${CYAN}${BOLD} ${EMOJI_COW} CowAgent Management Script${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo ""
|
|
echo -e "${YELLOW}Usage:${NC}"
|
|
echo -e " ${GREEN}./run.sh${NC} ${CYAN}# Install/Configure project${NC}"
|
|
echo -e " ${GREEN}./run.sh <command>${NC} ${CYAN}# Execute management command${NC}"
|
|
echo ""
|
|
echo -e "${YELLOW}Commands:${NC}"
|
|
echo -e " ${GREEN}start${NC} Start the service"
|
|
echo -e " ${GREEN}stop${NC} Stop the service"
|
|
echo -e " ${GREEN}restart${NC} Restart the service"
|
|
echo -e " ${GREEN}status${NC} Check service status"
|
|
echo -e " ${GREEN}logs${NC} View logs (tail -f)"
|
|
echo -e " ${GREEN}config${NC} Reconfigure project"
|
|
echo -e " ${GREEN}update${NC} Update and restart"
|
|
echo ""
|
|
echo -e "${YELLOW}Examples:${NC}"
|
|
echo -e " ${GREEN}./run.sh start${NC}"
|
|
echo -e " ${GREEN}./run.sh logs${NC}"
|
|
echo -e " ${GREEN}./run.sh status${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
}
|
|
|
|
# Ensure PYTHON_CMD is set
|
|
ensure_python_cmd() {
|
|
if [ -z "$PYTHON_CMD" ]; then
|
|
detect_python_command > /dev/null 2>&1 || PYTHON_CMD="python3"
|
|
fi
|
|
}
|
|
|
|
# Get service PID (empty string if not running)
|
|
get_pid() {
|
|
ensure_python_cmd > /dev/null 2>&1
|
|
ps ax | grep -i app.py | grep "${BASE_DIR}" | grep "$PYTHON_CMD" | grep -v grep | awk '{print $1}' | grep -E '^[0-9]+$'
|
|
}
|
|
|
|
# Check if service is running
|
|
is_running() {
|
|
[ -n "$(get_pid)" ]
|
|
}
|
|
|
|
# Start service
|
|
cmd_start() {
|
|
# Check if config.json exists
|
|
if [ ! -f "${BASE_DIR}/config.json" ]; then
|
|
echo -e "${RED}${EMOJI_CROSS} config.json not found${NC}"
|
|
echo -e "${YELLOW}Please run './run.sh' to configure first${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if is_running; then
|
|
echo -e "${YELLOW}${EMOJI_WARN} CowAgent is already running (PID: $(get_pid))${NC}"
|
|
echo -e "${YELLOW}Use './run.sh restart' to restart${NC}"
|
|
return
|
|
fi
|
|
|
|
check_python_version
|
|
start_project
|
|
}
|
|
|
|
# Stop service
|
|
cmd_stop() {
|
|
echo -e "${GREEN}${EMOJI_STOP} Stopping CowAgent...${NC}"
|
|
|
|
if ! is_running; then
|
|
echo -e "${YELLOW}${EMOJI_WARN} CowAgent is not running${NC}"
|
|
return
|
|
fi
|
|
|
|
pid=$(get_pid)
|
|
if [ -z "$pid" ] || ! echo "$pid" | grep -qE '^[0-9]+$'; then
|
|
echo -e "${RED}❌ Failed to get valid PID (got: ${pid})${NC}"
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Found running process (PID: ${pid})${NC}"
|
|
|
|
kill ${pid}
|
|
sleep 3
|
|
|
|
if ps -p ${pid} > /dev/null 2>&1; then
|
|
echo -e "${YELLOW}⚠️ Process not stopped, forcing termination...${NC}"
|
|
kill -9 ${pid}
|
|
fi
|
|
|
|
echo -e "${GREEN}${EMOJI_CHECK} CowAgent stopped${NC}"
|
|
}
|
|
|
|
# Restart service
|
|
cmd_restart() {
|
|
cmd_stop
|
|
sleep 1
|
|
cmd_start
|
|
}
|
|
|
|
# Check status
|
|
cmd_status() {
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${CYAN}${BOLD} ${EMOJI_COW} CowAgent Status${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
|
|
if is_running; then
|
|
pid=$(get_pid)
|
|
echo -e "${GREEN}Status:${NC} ✅ Running"
|
|
echo -e "${GREEN}PID:${NC} ${pid}"
|
|
if [ -f "${BASE_DIR}/nohup.out" ]; then
|
|
echo -e "${GREEN}Logs:${NC} ${BASE_DIR}/nohup.out"
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}Status:${NC} ⭐ Stopped"
|
|
fi
|
|
|
|
if [ -f "${BASE_DIR}/config.json" ]; then
|
|
model=$(grep -o '"model"[[:space:]]*:[[:space:]]*"[^"]*"' "${BASE_DIR}/config.json" | cut -d'"' -f4)
|
|
channel=$(grep -o '"channel_type"[[:space:]]*:[[:space:]]*"[^"]*"' "${BASE_DIR}/config.json" | cut -d'"' -f4)
|
|
echo -e "${GREEN}Model:${NC} ${model}"
|
|
echo -e "${GREEN}Channel:${NC} ${channel}"
|
|
fi
|
|
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
}
|
|
|
|
# View logs
|
|
cmd_logs() {
|
|
if [ -f "${BASE_DIR}/nohup.out" ]; then
|
|
echo -e "${YELLOW}Viewing logs (Ctrl+C to exit):${NC}"
|
|
tail -f "${BASE_DIR}/nohup.out"
|
|
else
|
|
echo -e "${RED}❌ Log file not found: ${BASE_DIR}/nohup.out${NC}"
|
|
fi
|
|
}
|
|
|
|
# Reconfigure
|
|
cmd_config() {
|
|
echo -e "${YELLOW}${EMOJI_WRENCH} Reconfiguring CowAgent...${NC}"
|
|
|
|
if [ -f "${BASE_DIR}/config.json" ]; then
|
|
backup_file="${BASE_DIR}/config.json.backup.$(date +%s)"
|
|
cp "${BASE_DIR}/config.json" "${backup_file}"
|
|
echo -e "${GREEN}✅ Backed up config to: ${backup_file}${NC}"
|
|
fi
|
|
|
|
check_python_version
|
|
install_dependencies
|
|
select_model
|
|
configure_model
|
|
select_channel
|
|
configure_channel
|
|
create_config_file
|
|
|
|
echo ""
|
|
read -p "Restart service now? [Y/n]: " restart_now
|
|
if [[ ! $restart_now == [Nn]* ]]; then
|
|
cmd_restart
|
|
fi
|
|
}
|
|
|
|
# Update project
|
|
cmd_update() {
|
|
echo -e "${GREEN}${EMOJI_WRENCH} Updating CowAgent...${NC}"
|
|
cd "${BASE_DIR}"
|
|
|
|
# Stop service
|
|
if is_running; then
|
|
cmd_stop
|
|
fi
|
|
|
|
# Update code
|
|
if [ -d .git ]; then
|
|
echo -e "${GREEN}🔄 Pulling latest code...${NC}"
|
|
git pull || {
|
|
echo -e "${YELLOW}⚠️ GitHub failed, trying Gitee...${NC}"
|
|
git remote set-url origin https://gitee.com/zhayujie/chatgpt-on-wechat.git
|
|
git pull
|
|
}
|
|
else
|
|
echo -e "${YELLOW}⚠️ Not a git repository, skipping code update${NC}"
|
|
fi
|
|
|
|
# Reinstall dependencies
|
|
check_python_version
|
|
install_dependencies
|
|
|
|
# Restart service
|
|
cmd_start
|
|
}
|
|
|
|
# Installation mode
|
|
install_mode() {
|
|
clear
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo -e "${CYAN}${BOLD} ${EMOJI_COW} CowAgent Installation${NC}"
|
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
|
echo ""
|
|
sleep 1
|
|
|
|
if [ "$IS_PROJECT_DIR" = true ]; then
|
|
echo -e "${GREEN}✅ Detected existing project directory.${NC}"
|
|
|
|
if [ -f "${BASE_DIR}/config.json" ]; then
|
|
echo -e "${GREEN}✅ Project already configured${NC}"
|
|
echo ""
|
|
show_usage
|
|
return
|
|
fi
|
|
|
|
echo -e "${YELLOW}📝 No config.json found. Let's configure your project!${NC}"
|
|
echo ""
|
|
|
|
# Project directory already exists, skip clone
|
|
check_python_version
|
|
else
|
|
# Remote install mode, need to clone project
|
|
check_python_version
|
|
clone_project
|
|
fi
|
|
|
|
# Install dependencies and configure
|
|
install_dependencies
|
|
select_model
|
|
configure_model
|
|
select_channel
|
|
configure_channel
|
|
create_config_file
|
|
|
|
echo ""
|
|
read -p "Start CowAgent now? [Y/n]: " start_now
|
|
if [[ ! $start_now == [Nn]* ]]; then
|
|
start_project
|
|
else
|
|
echo -e "${GREEN}✅ Installation complete!${NC}"
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}To start manually:${NC}"
|
|
echo -e "${YELLOW} cd ${BASE_DIR}${NC}"
|
|
echo -e "${YELLOW} ./run.sh start${NC}"
|
|
echo ""
|
|
echo -e "${CYAN}Or use nohup directly:${NC}"
|
|
echo -e "${YELLOW} nohup $PYTHON_CMD app.py > nohup.out 2>&1 & tail -f nohup.out${NC}"
|
|
fi
|
|
}
|
|
|
|
# Require running inside the project directory
|
|
require_project_dir() {
|
|
if [ "$IS_PROJECT_DIR" = false ]; then
|
|
echo -e "${RED}${EMOJI_CROSS} Must run in project directory${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
case "$1" in
|
|
start|stop|restart|status|logs|config|update)
|
|
require_project_dir
|
|
;;
|
|
esac
|
|
|
|
case "$1" in
|
|
start) cmd_start ;;
|
|
stop) cmd_stop ;;
|
|
restart) cmd_restart ;;
|
|
status) cmd_status ;;
|
|
logs) cmd_logs ;;
|
|
config) cmd_config ;;
|
|
update) cmd_update ;;
|
|
help|--help|-h)
|
|
show_usage
|
|
;;
|
|
"")
|
|
install_mode
|
|
;;
|
|
*)
|
|
echo -e "${RED}${EMOJI_CROSS} Unknown command: $1${NC}"
|
|
echo ""
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Execute main function
|
|
main "$@"
|