@yyy9527/openclaw-manager 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -0
- package/README.md +195 -0
- package/config/agents.yaml +22 -0
- package/config/channels.yaml +14 -0
- package/config/clawhub-skills.txt +6 -0
- package/config/model-env-templates/README.md +25 -0
- package/config/model-env-templates/dev.env.example +10 -0
- package/config/model-env-templates/master.env.example +10 -0
- package/config/model-env-templates/test.env.example +10 -0
- package/config/openclaw.env +20 -0
- package/config/openclaw.env.example +49 -0
- package/config/versions.lock +9 -0
- package/docker-compose.yml +24 -0
- package/package.json +47 -0
- package/scripts/apply_config.sh +33 -0
- package/scripts/bootstrap_models.sh +133 -0
- package/scripts/check_env.sh +55 -0
- package/scripts/health_check.sh +108 -0
- package/scripts/install_openclaw.sh +119 -0
- package/scripts/openclaw_ctl.sh +323 -0
- package/scripts/set_qwen_vendor_model.sh +142 -0
- package/scripts/setup_env.sh +156 -0
- package/scripts/skills_sync.sh +154 -0
- package/scripts/sync_model_configs.sh +217 -0
- package/src/integrations/feishu/notifier.js +46 -0
- package/src/orchestrator/index.js +30 -0
- package/src/tui/index.js +74 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[set-qwen] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fail() {
|
|
14
|
+
printf '[set-qwen][ERROR] %s\n' "$*" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
load_env() {
|
|
19
|
+
[ -f "${CONFIG_FILE}" ] || fail "缺少配置文件:${CONFIG_FILE}"
|
|
20
|
+
[ -f "${ENV_FILE}" ] || fail "缺少环境文件:${ENV_FILE}"
|
|
21
|
+
set -a
|
|
22
|
+
# shellcheck source=/dev/null
|
|
23
|
+
source "${CONFIG_FILE}"
|
|
24
|
+
# shellcheck source=/dev/null
|
|
25
|
+
source "${ENV_FILE}"
|
|
26
|
+
set +a
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
escape_env_value() {
|
|
30
|
+
local raw="$1"
|
|
31
|
+
raw="${raw//\\/\\\\}"
|
|
32
|
+
raw="${raw//\"/\\\"}"
|
|
33
|
+
printf '"%s"' "${raw}"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
upsert_env_key() {
|
|
37
|
+
local file="$1"
|
|
38
|
+
local key="$2"
|
|
39
|
+
local value="$3"
|
|
40
|
+
local escaped
|
|
41
|
+
local tmp_file
|
|
42
|
+
escaped="$(escape_env_value "${value}")"
|
|
43
|
+
tmp_file="$(mktemp)"
|
|
44
|
+
|
|
45
|
+
if [ -f "${file}" ]; then
|
|
46
|
+
awk -v key="${key}" -v val="${escaped}" '
|
|
47
|
+
BEGIN { updated = 0 }
|
|
48
|
+
$0 ~ "^[[:space:]]*" key "=" && updated == 0 {
|
|
49
|
+
print key "=" val
|
|
50
|
+
updated = 1
|
|
51
|
+
next
|
|
52
|
+
}
|
|
53
|
+
{ print }
|
|
54
|
+
END {
|
|
55
|
+
if (updated == 0) {
|
|
56
|
+
print key "=" val
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
' "${file}" > "${tmp_file}"
|
|
60
|
+
else
|
|
61
|
+
{
|
|
62
|
+
printf '# 由 set-model:qwen 生成;可按实例补充自定义键\n'
|
|
63
|
+
printf '%s=%s\n' "${key}" "${escaped}"
|
|
64
|
+
} > "${tmp_file}"
|
|
65
|
+
fi
|
|
66
|
+
mv "${tmp_file}" "${file}"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
resolve_template_file() {
|
|
70
|
+
local id="$1"
|
|
71
|
+
local template_dir="${PROJECT_ROOT}/config/model-env-templates"
|
|
72
|
+
local candidate_env="${template_dir}/${id}.env"
|
|
73
|
+
local candidate_example="${template_dir}/${id}.env.example"
|
|
74
|
+
if [ -f "${candidate_env}" ]; then
|
|
75
|
+
printf '%s\n' "${candidate_env}"
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
if [ -f "${candidate_example}" ]; then
|
|
79
|
+
printf '%s\n' "${candidate_example}"
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
write_instance_model_env() {
|
|
86
|
+
local id="$1"
|
|
87
|
+
local home_dir="${OPENCLAW_HOME_ROOT}/${id}"
|
|
88
|
+
local model_env="${home_dir}/model.env"
|
|
89
|
+
local template_file
|
|
90
|
+
|
|
91
|
+
MODEL_PROVIDER_ID="openai"
|
|
92
|
+
MODEL_PRIMARY="openai/${QWEN_MODEL:-qwen-max}"
|
|
93
|
+
MODEL_BASE_URL="${QWEN_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}"
|
|
94
|
+
MODEL_API_KEY_ENV="${QWEN_API_KEY_ENV:-DASHSCOPE_API_KEY}"
|
|
95
|
+
MODEL_API="openai-completions"
|
|
96
|
+
MODEL_ID="${QWEN_MODEL:-qwen-max}"
|
|
97
|
+
MODEL_NAME="Qwen Vendor"
|
|
98
|
+
MODEL_CONTEXT_WINDOW=131072
|
|
99
|
+
MODEL_MAX_TOKENS=8192
|
|
100
|
+
|
|
101
|
+
if template_file="$(resolve_template_file "${id}")"; then
|
|
102
|
+
# shellcheck source=/dev/null
|
|
103
|
+
source "${template_file}"
|
|
104
|
+
log "实例 ${id} 使用模板:${template_file}"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
mkdir -p "${home_dir}"
|
|
108
|
+
if [ ! -f "${model_env}" ]; then
|
|
109
|
+
printf '# 由 set-model:qwen 生成;可按实例补充自定义键\n' > "${model_env}"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# 仅更新受控键,避免覆盖用户自定义键。
|
|
113
|
+
upsert_env_key "${model_env}" "MODEL_PROVIDER_ID" "${MODEL_PROVIDER_ID}"
|
|
114
|
+
upsert_env_key "${model_env}" "MODEL_PRIMARY" "${MODEL_PRIMARY}"
|
|
115
|
+
upsert_env_key "${model_env}" "MODEL_BASE_URL" "${MODEL_BASE_URL}"
|
|
116
|
+
upsert_env_key "${model_env}" "MODEL_API_KEY_ENV" "${MODEL_API_KEY_ENV}"
|
|
117
|
+
upsert_env_key "${model_env}" "MODEL_API" "${MODEL_API}"
|
|
118
|
+
upsert_env_key "${model_env}" "MODEL_ID" "${MODEL_ID}"
|
|
119
|
+
upsert_env_key "${model_env}" "MODEL_NAME" "${MODEL_NAME}"
|
|
120
|
+
upsert_env_key "${model_env}" "MODEL_CONTEXT_WINDOW" "${MODEL_CONTEXT_WINDOW}"
|
|
121
|
+
upsert_env_key "${model_env}" "MODEL_MAX_TOKENS" "${MODEL_MAX_TOKENS}"
|
|
122
|
+
log "实例 ${id} 模型配置已安全更新:${model_env}"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main() {
|
|
126
|
+
load_env
|
|
127
|
+
[ -n "${OPENCLAW_INSTANCE_IDS:-}" ] || fail "OPENCLAW_INSTANCE_IDS 未配置"
|
|
128
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
129
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-${HOME}/openclaws}"
|
|
130
|
+
fi
|
|
131
|
+
[ -n "${QWEN_API_KEY:-${DASHSCOPE_API_KEY:-}}" ] || fail "缺少 QWEN_API_KEY 或 DASHSCOPE_API_KEY"
|
|
132
|
+
|
|
133
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
134
|
+
for id in "${instance_list[@]}"; do
|
|
135
|
+
write_instance_model_env "${id}"
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
bash "${PROJECT_ROOT}/scripts/sync_model_configs.sh"
|
|
139
|
+
log "Qwen 厂商模型配置已写入各实例家目录。执行 npm run apply-config 使其生效。"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main "$@"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[setup_env] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
warn() {
|
|
14
|
+
printf '[setup_env][WARN] %s\n' "$*" >&2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
require_cmd() {
|
|
18
|
+
local cmd="$1"
|
|
19
|
+
local install_hint="$2"
|
|
20
|
+
if command -v "${cmd}" >/dev/null 2>&1; then
|
|
21
|
+
log "已检测到 ${cmd}"
|
|
22
|
+
else
|
|
23
|
+
warn "未检测到 ${cmd}。建议执行:${install_hint}"
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
install_with_brew_if_missing() {
|
|
29
|
+
local formula="$1"
|
|
30
|
+
local check_cmd="$2"
|
|
31
|
+
if command -v "${check_cmd}" >/dev/null 2>&1; then
|
|
32
|
+
log "${formula} 已存在,跳过安装"
|
|
33
|
+
return 0
|
|
34
|
+
fi
|
|
35
|
+
log "通过 Homebrew 安装 ${formula}"
|
|
36
|
+
brew install "${formula}"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
install_pnpm_if_missing() {
|
|
40
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
41
|
+
log "pnpm 已存在,跳过安装"
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
log "通过 npm 安装 pnpm"
|
|
45
|
+
npm install -g pnpm
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ensure_pnpm_home() {
|
|
49
|
+
export PNPM_HOME="${PNPM_HOME:-${HOME}/Library/pnpm}"
|
|
50
|
+
mkdir -p "${PNPM_HOME}"
|
|
51
|
+
case ":${PATH}:" in
|
|
52
|
+
*":${PNPM_HOME}:"*) ;;
|
|
53
|
+
*) export PATH="${PNPM_HOME}:${PATH}" ;;
|
|
54
|
+
esac
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
install_clawhub_with_pnpm_if_missing() {
|
|
58
|
+
if command -v clawhub >/dev/null 2>&1; then
|
|
59
|
+
log "clawhub 已存在,跳过安装"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
log "通过 pnpm 全局安装 clawhub(失败将自动重试)"
|
|
63
|
+
local max_retries=3
|
|
64
|
+
local attempt=1
|
|
65
|
+
while [ "${attempt}" -le "${max_retries}" ]; do
|
|
66
|
+
if pnpm add -g clawhub; then
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
warn "pnpm 安装 clawhub 第 ${attempt}/${max_retries} 次失败,准备重试。"
|
|
70
|
+
sleep 2
|
|
71
|
+
attempt=$((attempt + 1))
|
|
72
|
+
done
|
|
73
|
+
warn "pnpm 安装 clawhub 多次失败,请手动执行:pnpm add -g clawhub"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
install_cask_with_brew_if_missing() {
|
|
77
|
+
local cask="$1"
|
|
78
|
+
local app_check="$2"
|
|
79
|
+
if [ -e "${app_check}" ]; then
|
|
80
|
+
log "${cask} 已存在,跳过安装"
|
|
81
|
+
return 0
|
|
82
|
+
fi
|
|
83
|
+
log "通过 Homebrew Cask 安装 ${cask}"
|
|
84
|
+
brew install --cask "${cask}"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
copy_template_if_missing() {
|
|
88
|
+
local src="$1"
|
|
89
|
+
local dst="$2"
|
|
90
|
+
if [ -f "${dst}" ]; then
|
|
91
|
+
log "${dst} 已存在,跳过"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
mkdir -p "$(dirname "${dst}")"
|
|
95
|
+
cp "${src}" "${dst}"
|
|
96
|
+
log "已创建 ${dst}"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main() {
|
|
100
|
+
log "开始初始化 macOS 环境(幂等执行)"
|
|
101
|
+
|
|
102
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
103
|
+
warn "当前系统不是 macOS,脚本可能不适用。"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
107
|
+
warn "未检测到 Homebrew,请先安装:"
|
|
108
|
+
warn "/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
install_with_brew_if_missing "node" "node"
|
|
113
|
+
install_with_brew_if_missing "jq" "jq"
|
|
114
|
+
install_with_brew_if_missing "yq" "yq"
|
|
115
|
+
install_with_brew_if_missing "gh" "gh"
|
|
116
|
+
install_cask_with_brew_if_missing "docker" "/Applications/Docker.app"
|
|
117
|
+
install_with_brew_if_missing "ollama" "ollama"
|
|
118
|
+
|
|
119
|
+
install_pnpm_if_missing
|
|
120
|
+
ensure_pnpm_home
|
|
121
|
+
install_clawhub_with_pnpm_if_missing
|
|
122
|
+
|
|
123
|
+
require_cmd "node" "brew install node"
|
|
124
|
+
require_cmd "npm" "brew install node"
|
|
125
|
+
require_cmd "pnpm" "npm install -g pnpm"
|
|
126
|
+
require_cmd "docker" "brew install --cask docker"
|
|
127
|
+
require_cmd "ollama" "brew install ollama"
|
|
128
|
+
|
|
129
|
+
if ! docker info >/dev/null 2>&1; then
|
|
130
|
+
warn "Docker Desktop 似乎未启动,请手动启动后重试脚本。"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
log "Docker daemon 状态正常"
|
|
134
|
+
|
|
135
|
+
copy_template_if_missing "${PROJECT_ROOT}/config/openclaw.env.example" "${CONFIG_FILE}"
|
|
136
|
+
copy_template_if_missing "${PROJECT_ROOT}/.env.example" "${ENV_FILE}"
|
|
137
|
+
|
|
138
|
+
set -a
|
|
139
|
+
# shellcheck source=/dev/null
|
|
140
|
+
source "${CONFIG_FILE}"
|
|
141
|
+
set +a
|
|
142
|
+
|
|
143
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
144
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-${HOME}/openclaws}"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
mkdir -p "${OPENCLAW_HOME_ROOT}" "${PROJECT_ROOT}/logs"
|
|
148
|
+
log "已确保实例家目录根路径存在:${OPENCLAW_HOME_ROOT}"
|
|
149
|
+
log "已确保 logs 目录存在"
|
|
150
|
+
|
|
151
|
+
"${PROJECT_ROOT}/scripts/skills_sync.sh"
|
|
152
|
+
|
|
153
|
+
log "环境初始化完成"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
main "$@"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
|
|
8
|
+
log() {
|
|
9
|
+
printf '[skills-sync] %s\n' "$*"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
warn() {
|
|
13
|
+
printf '[skills-sync][WARN] %s\n' "$*" >&2
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
load_env() {
|
|
17
|
+
[ -f "${CONFIG_FILE}" ] || {
|
|
18
|
+
warn "缺少配置文件:${CONFIG_FILE}"
|
|
19
|
+
exit 1
|
|
20
|
+
}
|
|
21
|
+
set -a
|
|
22
|
+
# shellcheck source=/dev/null
|
|
23
|
+
source "${CONFIG_FILE}"
|
|
24
|
+
set +a
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
install_slug_with_retry() {
|
|
28
|
+
local slug="$1"
|
|
29
|
+
local max_retries="${OPENCLAW_CLAWHUB_INSTALL_RETRIES:-4}"
|
|
30
|
+
local attempt=1
|
|
31
|
+
while [ "${attempt}" -le "${max_retries}" ]; do
|
|
32
|
+
local output
|
|
33
|
+
output="$(clawhub --no-input --workdir "${OPENCLAW_HOME_ROOT}/shared" --dir skills install "${slug}" 2>&1 || true)"
|
|
34
|
+
if printf '%s' "${output}" | awk '/Installed|Up to date/{found=1} END{exit found?0:1}'; then
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
if printf '%s' "${output}" | awk '/Rate limit exceeded|Too many requests/{found=1} END{exit found?0:1}'; then
|
|
38
|
+
warn "clawhub install ${slug} 命中限流,第 ${attempt}/${max_retries} 次,退避后重试。"
|
|
39
|
+
sleep $((attempt * 5))
|
|
40
|
+
else
|
|
41
|
+
warn "clawhub install ${slug} 第 ${attempt}/${max_retries} 次失败:$(printf '%s' "${output}" | tr '\n' ' ' | cut -c1-140)"
|
|
42
|
+
sleep 2
|
|
43
|
+
fi
|
|
44
|
+
attempt=$((attempt + 1))
|
|
45
|
+
done
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
resolve_best_slug() {
|
|
50
|
+
local query="$1"
|
|
51
|
+
local result
|
|
52
|
+
result="$(clawhub --no-input search "${query}" 2>/dev/null || true)"
|
|
53
|
+
printf '%s\n' "${result}" | awk 'NR>1 && $1 !~ /^-/ {print $1; exit}'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
search_slugs() {
|
|
57
|
+
local query="$1"
|
|
58
|
+
local result
|
|
59
|
+
result="$(clawhub --no-input search "${query}" 2>/dev/null || true)"
|
|
60
|
+
printf '%s\n' "${result}" | awk 'NR>1 && $1 !~ /^-/ {print $1}'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ensure_dirs() {
|
|
64
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
65
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-${HOME}/openclaws}"
|
|
66
|
+
fi
|
|
67
|
+
OPENCLAW_SHARED_SKILLS_DIR="${OPENCLAW_SHARED_SKILLS_DIR:-${OPENCLAW_HOME_ROOT}/shared/skills}"
|
|
68
|
+
|
|
69
|
+
mkdir -p "${OPENCLAW_SHARED_SKILLS_DIR}"
|
|
70
|
+
log "共享技能目录:${OPENCLAW_SHARED_SKILLS_DIR}"
|
|
71
|
+
|
|
72
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
73
|
+
for id in "${instance_list[@]}"; do
|
|
74
|
+
local_home="${OPENCLAW_HOME_ROOT}/${id}"
|
|
75
|
+
local_skills="${local_home}/skills"
|
|
76
|
+
mkdir -p "${local_skills}"
|
|
77
|
+
printf "instance=%s\n" "${id}" > "${local_home}/.openclaw-home"
|
|
78
|
+
log "实例技能目录:${local_skills}"
|
|
79
|
+
done
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
install_from_clawhub() {
|
|
83
|
+
local skills_file="${PROJECT_ROOT}/config/clawhub-skills.txt"
|
|
84
|
+
local enabled="${OPENCLAW_ENABLE_CLAWHUB_SKILLS:-true}"
|
|
85
|
+
if [ "${enabled}" != "true" ]; then
|
|
86
|
+
log "已禁用 clawhub 自动安装,跳过"
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if ! command -v clawhub >/dev/null 2>&1; then
|
|
91
|
+
warn "未检测到 clawhub 命令,跳过技能自动安装。"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if ! clawhub whoami >/dev/null 2>&1; then
|
|
96
|
+
warn "clawhub 未登录,可能触发限流导致安装失败。建议先执行:npm run clawhub:login"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if [ ! -f "${skills_file}" ]; then
|
|
100
|
+
warn "未找到 ${skills_file},跳过技能自动安装。"
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
log "优先安装 find-skills 技能用于检索能力扩展"
|
|
105
|
+
if install_slug_with_retry "find-skills"; then
|
|
106
|
+
log "find-skills 安装成功"
|
|
107
|
+
else
|
|
108
|
+
warn "find-skills 安装失败,后续继续搜索但不自动安装不确定技能。"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
while IFS= read -r skill || [ -n "${skill}" ]; do
|
|
112
|
+
skill="$(printf '%s' "${skill}" | xargs)"
|
|
113
|
+
[ -z "${skill}" ] && continue
|
|
114
|
+
[[ "${skill}" =~ ^# ]] && continue
|
|
115
|
+
|
|
116
|
+
log "通过 find-skills 流程检索:${skill}"
|
|
117
|
+
local normalized
|
|
118
|
+
normalized="$(printf '%s' "${skill}" | tr ' ' '-')"
|
|
119
|
+
local slugs
|
|
120
|
+
slugs="$(search_slugs "${skill}")"
|
|
121
|
+
if [ -z "${slugs}" ]; then
|
|
122
|
+
warn "未搜索到可用技能:${skill}"
|
|
123
|
+
continue
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
local matched=""
|
|
127
|
+
while IFS= read -r slug; do
|
|
128
|
+
if [ "${slug}" = "${normalized}" ] || [ "${slug}" = "${skill}" ]; then
|
|
129
|
+
matched="${slug}"
|
|
130
|
+
break
|
|
131
|
+
fi
|
|
132
|
+
done <<< "${slugs}"
|
|
133
|
+
|
|
134
|
+
if [ -n "${matched}" ]; then
|
|
135
|
+
log "命中精确 slug,开始安装:${matched}"
|
|
136
|
+
if ! install_slug_with_retry "${matched}"; then
|
|
137
|
+
warn "技能 ${matched} 安装失败,请稍后重试。"
|
|
138
|
+
fi
|
|
139
|
+
continue
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
warn "未找到精确匹配 slug(${normalized}),为避免误装已跳过自动安装。"
|
|
143
|
+
warn "建议候选(前3):$(printf '%s\n' "${slugs}" | head -n 3 | tr '\n' ' ')"
|
|
144
|
+
done < "${skills_file}"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main() {
|
|
148
|
+
load_env
|
|
149
|
+
ensure_dirs
|
|
150
|
+
install_from_clawhub
|
|
151
|
+
log "技能目录同步完成"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main "$@"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[sync-model] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
warn() {
|
|
14
|
+
printf '[sync-model][WARN] %s\n' "$*" >&2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
load_env() {
|
|
18
|
+
[ -f "${CONFIG_FILE}" ] || {
|
|
19
|
+
warn "缺少配置文件:${CONFIG_FILE}"
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
set -a
|
|
23
|
+
# shellcheck source=/dev/null
|
|
24
|
+
source "${CONFIG_FILE}"
|
|
25
|
+
if [ -f "${ENV_FILE}" ]; then
|
|
26
|
+
# shellcheck source=/dev/null
|
|
27
|
+
source "${ENV_FILE}"
|
|
28
|
+
fi
|
|
29
|
+
set +a
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
escape_env_value() {
|
|
33
|
+
local raw="$1"
|
|
34
|
+
raw="${raw//\\/\\\\}"
|
|
35
|
+
raw="${raw//\"/\\\"}"
|
|
36
|
+
printf '"%s"' "${raw}"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
upsert_env_key() {
|
|
40
|
+
local file="$1"
|
|
41
|
+
local key="$2"
|
|
42
|
+
local value="$3"
|
|
43
|
+
local escaped
|
|
44
|
+
local tmp_file
|
|
45
|
+
escaped="$(escape_env_value "${value}")"
|
|
46
|
+
tmp_file="$(mktemp)"
|
|
47
|
+
|
|
48
|
+
if [ -f "${file}" ]; then
|
|
49
|
+
awk -v key="${key}" -v val="${escaped}" '
|
|
50
|
+
BEGIN { updated = 0 }
|
|
51
|
+
$0 ~ "^[[:space:]]*" key "=" && updated == 0 {
|
|
52
|
+
print key "=" val
|
|
53
|
+
updated = 1
|
|
54
|
+
next
|
|
55
|
+
}
|
|
56
|
+
{ print }
|
|
57
|
+
END {
|
|
58
|
+
if (updated == 0) {
|
|
59
|
+
print key "=" val
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
' "${file}" > "${tmp_file}"
|
|
63
|
+
else
|
|
64
|
+
{
|
|
65
|
+
printf '# 每个实例独立模型配置(修改后执行 npm run apply-config 生效)\n'
|
|
66
|
+
printf '%s=%s\n' "${key}" "${escaped}"
|
|
67
|
+
} > "${tmp_file}"
|
|
68
|
+
fi
|
|
69
|
+
mv "${tmp_file}" "${file}"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
resolve_template_file() {
|
|
73
|
+
local id="$1"
|
|
74
|
+
local template_dir="${PROJECT_ROOT}/config/model-env-templates"
|
|
75
|
+
local candidate_env="${template_dir}/${id}.env"
|
|
76
|
+
local candidate_example="${template_dir}/${id}.env.example"
|
|
77
|
+
if [ -f "${candidate_env}" ]; then
|
|
78
|
+
printf '%s\n' "${candidate_env}"
|
|
79
|
+
return 0
|
|
80
|
+
fi
|
|
81
|
+
if [ -f "${candidate_example}" ]; then
|
|
82
|
+
printf '%s\n' "${candidate_example}"
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ensure_default_model_env() {
|
|
89
|
+
local instance_id="$1"
|
|
90
|
+
local home_dir="${OPENCLAW_HOME_ROOT}/${instance_id}"
|
|
91
|
+
local model_env="${home_dir}/model.env"
|
|
92
|
+
local template_file
|
|
93
|
+
|
|
94
|
+
mkdir -p "${home_dir}"
|
|
95
|
+
if [ -f "${model_env}" ]; then
|
|
96
|
+
return 0
|
|
97
|
+
fi
|
|
98
|
+
printf '# 每个实例独立模型配置(修改后执行 npm run apply-config 生效)\n' > "${model_env}"
|
|
99
|
+
|
|
100
|
+
MODEL_PROVIDER_ID="openai"
|
|
101
|
+
MODEL_PRIMARY="openai/${QWEN_MODEL:-qwen-max}"
|
|
102
|
+
MODEL_BASE_URL="${QWEN_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}"
|
|
103
|
+
MODEL_API_KEY_ENV="${QWEN_API_KEY_ENV:-DASHSCOPE_API_KEY}"
|
|
104
|
+
MODEL_API="openai-completions"
|
|
105
|
+
MODEL_ID="${QWEN_MODEL:-qwen-max}"
|
|
106
|
+
MODEL_NAME="Qwen Vendor"
|
|
107
|
+
MODEL_CONTEXT_WINDOW=131072
|
|
108
|
+
MODEL_MAX_TOKENS=8192
|
|
109
|
+
|
|
110
|
+
if template_file="$(resolve_template_file "${instance_id}")"; then
|
|
111
|
+
# shellcheck source=/dev/null
|
|
112
|
+
source "${template_file}"
|
|
113
|
+
log "实例 ${instance_id} 使用模板:${template_file}"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# 仅补齐受控键,避免覆盖用户追加的自定义配置。
|
|
117
|
+
upsert_env_key "${model_env}" "MODEL_PROVIDER_ID" "${MODEL_PROVIDER_ID}"
|
|
118
|
+
upsert_env_key "${model_env}" "MODEL_PRIMARY" "${MODEL_PRIMARY}"
|
|
119
|
+
upsert_env_key "${model_env}" "MODEL_BASE_URL" "${MODEL_BASE_URL}"
|
|
120
|
+
upsert_env_key "${model_env}" "MODEL_API_KEY_ENV" "${MODEL_API_KEY_ENV}"
|
|
121
|
+
upsert_env_key "${model_env}" "MODEL_API" "${MODEL_API}"
|
|
122
|
+
upsert_env_key "${model_env}" "MODEL_ID" "${MODEL_ID}"
|
|
123
|
+
upsert_env_key "${model_env}" "MODEL_NAME" "${MODEL_NAME}"
|
|
124
|
+
upsert_env_key "${model_env}" "MODEL_CONTEXT_WINDOW" "${MODEL_CONTEXT_WINDOW}"
|
|
125
|
+
upsert_env_key "${model_env}" "MODEL_MAX_TOKENS" "${MODEL_MAX_TOKENS}"
|
|
126
|
+
log "已生成默认模型配置:${model_env}"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
sync_instance_model() {
|
|
130
|
+
local instance_id="$1"
|
|
131
|
+
local home_dir="${OPENCLAW_HOME_ROOT}/${instance_id}"
|
|
132
|
+
local model_env="${home_dir}/model.env"
|
|
133
|
+
local openclaw_json="${home_dir}/openclaw.json"
|
|
134
|
+
local resolved_api_key=""
|
|
135
|
+
|
|
136
|
+
ensure_default_model_env "${instance_id}"
|
|
137
|
+
|
|
138
|
+
set -a
|
|
139
|
+
# shellcheck source=/dev/null
|
|
140
|
+
source "${model_env}"
|
|
141
|
+
set +a
|
|
142
|
+
|
|
143
|
+
: "${MODEL_PROVIDER_ID:?MODEL_PROVIDER_ID 未配置}"
|
|
144
|
+
: "${MODEL_PRIMARY:?MODEL_PRIMARY 未配置}"
|
|
145
|
+
: "${MODEL_BASE_URL:?MODEL_BASE_URL 未配置}"
|
|
146
|
+
: "${MODEL_API_KEY_ENV:?MODEL_API_KEY_ENV 未配置}"
|
|
147
|
+
: "${MODEL_API:?MODEL_API 未配置}"
|
|
148
|
+
: "${MODEL_ID:?MODEL_ID 未配置}"
|
|
149
|
+
: "${MODEL_NAME:?MODEL_NAME 未配置}"
|
|
150
|
+
: "${MODEL_CONTEXT_WINDOW:?MODEL_CONTEXT_WINDOW 未配置}"
|
|
151
|
+
: "${MODEL_MAX_TOKENS:?MODEL_MAX_TOKENS 未配置}"
|
|
152
|
+
|
|
153
|
+
# MODEL_API_KEY_ENV 兼容两种写法:
|
|
154
|
+
# 1) 环境变量名(推荐),例如 DASHSCOPE_API_KEY
|
|
155
|
+
# 2) 直接填写 API Key(不推荐,但兼容历史配置)
|
|
156
|
+
if [ -n "${!MODEL_API_KEY_ENV:-}" ]; then
|
|
157
|
+
resolved_api_key="${!MODEL_API_KEY_ENV}"
|
|
158
|
+
else
|
|
159
|
+
resolved_api_key="${MODEL_API_KEY_ENV}"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if [ -z "${resolved_api_key}" ]; then
|
|
163
|
+
warn "实例 ${instance_id} 未解析到可用 API Key(MODEL_API_KEY_ENV=${MODEL_API_KEY_ENV})"
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
[ -f "${openclaw_json}" ] || printf '{}\n' > "${openclaw_json}"
|
|
168
|
+
local tmp_file
|
|
169
|
+
tmp_file="$(mktemp)"
|
|
170
|
+
|
|
171
|
+
jq \
|
|
172
|
+
--arg provider_id "${MODEL_PROVIDER_ID}" \
|
|
173
|
+
--arg primary "${MODEL_PRIMARY}" \
|
|
174
|
+
--arg base_url "${MODEL_BASE_URL}" \
|
|
175
|
+
--arg api_key "${resolved_api_key}" \
|
|
176
|
+
--arg api_type "${MODEL_API}" \
|
|
177
|
+
--arg model_id "${MODEL_ID}" \
|
|
178
|
+
--arg model_name "${MODEL_NAME}" \
|
|
179
|
+
--argjson context_window "${MODEL_CONTEXT_WINDOW}" \
|
|
180
|
+
--argjson max_tokens "${MODEL_MAX_TOKENS}" \
|
|
181
|
+
'
|
|
182
|
+
.agents.defaults.model.primary = $primary
|
|
183
|
+
| .models.providers[$provider_id].baseUrl = $base_url
|
|
184
|
+
| .models.providers[$provider_id].apiKey = $api_key
|
|
185
|
+
| .models.providers[$provider_id].api = $api_type
|
|
186
|
+
| .models.providers[$provider_id].models = [
|
|
187
|
+
{
|
|
188
|
+
id: $model_id,
|
|
189
|
+
name: $model_name,
|
|
190
|
+
reasoning: false,
|
|
191
|
+
input: ["text"],
|
|
192
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
193
|
+
contextWindow: $context_window,
|
|
194
|
+
maxTokens: $max_tokens
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
' "${openclaw_json}" > "${tmp_file}"
|
|
198
|
+
|
|
199
|
+
mv "${tmp_file}" "${openclaw_json}"
|
|
200
|
+
log "实例 ${instance_id} 模型配置已同步到家目录"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
main() {
|
|
204
|
+
load_env
|
|
205
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
206
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-${HOME}/openclaws}"
|
|
207
|
+
fi
|
|
208
|
+
: "${OPENCLAW_INSTANCE_IDS:?OPENCLAW_INSTANCE_IDS 未配置}"
|
|
209
|
+
|
|
210
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
211
|
+
for id in "${instance_list[@]}"; do
|
|
212
|
+
sync_instance_model "${id}"
|
|
213
|
+
done
|
|
214
|
+
log "全部实例模型配置同步完成"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
main "$@"
|