ylib-syim 0.0.1
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/README.md +376 -0
- package/bin/syim.mjs +34 -0
- package/bridges/dingtalk-stdio-bridge.ts +10 -0
- package/bridges/lark-stdio-bridge.ts +10 -0
- package/bridges/logger.ts +47 -0
- package/bridges/main.ts +126 -0
- package/package.json +56 -0
- package/scripts/bridge-runtime-package.json +14 -0
- package/scripts/build-bridge.mjs +36 -0
- package/scripts/dev-server.ts +53 -0
- package/scripts/dingtalk-stdio-bridge.ts +309 -0
- package/scripts/dingtalk-stream-server.ts +173 -0
- package/scripts/example-feishu-webhook.ts +71 -0
- package/scripts/example-iflow-agent.ts +117 -0
- package/scripts/example-single-agent.ts +151 -0
- package/scripts/feishu-yuce-bridge.ts +226 -0
- package/scripts/install/README.md +98 -0
- package/scripts/install/im-agent-hub-ctl.sh +283 -0
- package/scripts/install/install-dingtalk-bridge.sh +314 -0
- package/scripts/install/systemd/im-agent-hub-dingtalk.service.example +18 -0
- package/scripts/install/uninstall-im-agent-hub.sh +140 -0
- package/scripts/lark-stdio-bridge.ts +387 -0
- package/scripts/start.sh +12 -0
- package/syim.json.bak +70 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 全局管理命令:im-agent-hub-ctl(独立 im-agent-hub 项目)
|
|
3
|
+
# 配置来自 ~/.im-agent-hub/manifest.env
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
DEFAULT_HOME="${HOME:?}/.im-agent-hub"
|
|
8
|
+
MANIFEST="${IM_AGENT_HUB_HOME:-$DEFAULT_HOME}/manifest.env"
|
|
9
|
+
PID_FILE=""
|
|
10
|
+
LOG_FILE=""
|
|
11
|
+
|
|
12
|
+
load_manifest() {
|
|
13
|
+
if [[ ! -f "$MANIFEST" ]]; then
|
|
14
|
+
echo "未找到 $MANIFEST ,请先运行 scripts/install/install-dingtalk-bridge.sh 安装。" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
# shellcheck disable=SC1090
|
|
18
|
+
source "$MANIFEST"
|
|
19
|
+
IM_AGENT_HUB_HOME="${IM_AGENT_HUB_HOME:-$DEFAULT_HOME}"
|
|
20
|
+
PID_FILE="$IM_AGENT_HUB_HOME/run/dingtalk-stdio-bridge.pid"
|
|
21
|
+
LOG_FILE="$IM_AGENT_HUB_HOME/logs/dingtalk-stdio-bridge.log"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ensure_dirs() {
|
|
25
|
+
mkdir -p "$(dirname "$PID_FILE")" "$(dirname "$LOG_FILE")"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
nvm_bootstrap() {
|
|
29
|
+
if [[ -f "$HOME/.nvm/nvm.sh" ]]; then
|
|
30
|
+
# shellcheck disable=SC1090
|
|
31
|
+
source "$HOME/.nvm/nvm.sh"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
cmd_start() {
|
|
36
|
+
load_manifest
|
|
37
|
+
ensure_dirs
|
|
38
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
39
|
+
local old
|
|
40
|
+
old="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
41
|
+
if [[ -n "$old" ]] && kill -0 "$old" 2>/dev/null; then
|
|
42
|
+
echo "已在运行 (pid=$old)"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
rm -f "$PID_FILE"
|
|
46
|
+
fi
|
|
47
|
+
nvm_bootstrap
|
|
48
|
+
cd "$IM_AGENT_HUB_PACKAGE_ROOT"
|
|
49
|
+
echo "工作目录: $IM_AGENT_HUB_PACKAGE_ROOT" >>"$LOG_FILE"
|
|
50
|
+
echo "---- $(date -Iseconds) start ----" >>"$LOG_FILE"
|
|
51
|
+
# 若已执行 npm run build:bridge,优先用单文件 bundle(CJS,避免 ws 等对 events 的 dynamic require 在 ESM 包里报错)
|
|
52
|
+
if [[ -f "$IM_AGENT_HUB_PACKAGE_ROOT/dist/dingtalk-stdio-bridge.cjs" ]]; then
|
|
53
|
+
echo "[start] 使用打包产物: node dist/dingtalk-stdio-bridge.cjs" >>"$LOG_FILE"
|
|
54
|
+
nohup node dist/dingtalk-stdio-bridge.cjs >>"$LOG_FILE" 2>&1 &
|
|
55
|
+
elif [[ -f "$IM_AGENT_HUB_PACKAGE_ROOT/dist/dingtalk-stdio-bridge.mjs" ]]; then
|
|
56
|
+
echo "[start] 使用旧版 .mjs 产物(建议改为 npm run build:bridge 生成 .cjs)" >>"$LOG_FILE"
|
|
57
|
+
nohup node dist/dingtalk-stdio-bridge.mjs >>"$LOG_FILE" 2>&1 &
|
|
58
|
+
else
|
|
59
|
+
echo "[start] 使用源码: npx tsx scripts/dingtalk-stdio-bridge.ts" >>"$LOG_FILE"
|
|
60
|
+
nohup npx tsx scripts/dingtalk-stdio-bridge.ts >>"$LOG_FILE" 2>&1 &
|
|
61
|
+
fi
|
|
62
|
+
echo $! >"$PID_FILE"
|
|
63
|
+
echo "已启动 dingtalk-stdio-bridge,pid=$(cat "$PID_FILE"),日志: $LOG_FILE"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cmd_stop() {
|
|
67
|
+
load_manifest
|
|
68
|
+
if [[ ! -f "$PID_FILE" ]]; then
|
|
69
|
+
echo "未找到 pid 文件,可能未启动"
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
local pid
|
|
73
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
74
|
+
if [[ -z "$pid" ]]; then
|
|
75
|
+
rm -f "$PID_FILE"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
79
|
+
kill "$pid" 2>/dev/null || true
|
|
80
|
+
sleep 1
|
|
81
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
82
|
+
fi
|
|
83
|
+
rm -f "$PID_FILE"
|
|
84
|
+
echo "已停止"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
cmd_restart() {
|
|
88
|
+
cmd_stop || true
|
|
89
|
+
cmd_start
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cmd_status() {
|
|
93
|
+
load_manifest
|
|
94
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
95
|
+
local pid
|
|
96
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
97
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
98
|
+
echo "运行中 pid=$pid"
|
|
99
|
+
echo "项目目录: $IM_AGENT_HUB_PACKAGE_ROOT"
|
|
100
|
+
echo "日志: $LOG_FILE"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
echo "未运行"
|
|
105
|
+
exit 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
cmd_logs() {
|
|
109
|
+
load_manifest
|
|
110
|
+
touch "$LOG_FILE"
|
|
111
|
+
tail -n "${2:-100}" -f "$LOG_FILE"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
cmd_config() {
|
|
115
|
+
load_manifest
|
|
116
|
+
"${EDITOR:-vi}" "$IM_AGENT_HUB_PACKAGE_ROOT/openclaw.json"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
cmd_update_help() {
|
|
120
|
+
cat <<'EOF'
|
|
121
|
+
更新流程(推荐一条命令)
|
|
122
|
+
|
|
123
|
+
im-agent-hub-ctl update
|
|
124
|
+
|
|
125
|
+
等价于(在 manifest 记录的项目目录下):
|
|
126
|
+
|
|
127
|
+
1. 可选:备份 openclaw.json → openclaw.json.bak.<时间戳>
|
|
128
|
+
2. 停止桥接进程
|
|
129
|
+
3. git pull(若非 git 仓库则跳过,仅 npm install)
|
|
130
|
+
4. npm install
|
|
131
|
+
5. 重新启动桥接
|
|
132
|
+
|
|
133
|
+
会保留的内容
|
|
134
|
+
|
|
135
|
+
- openclaw.json(git pull 若与远端冲突需手动解决;备份文件在项目根目录)
|
|
136
|
+
- ~/.im-agent-hub/manifest.env(安装路径不变则无需重装)
|
|
137
|
+
|
|
138
|
+
环境变量
|
|
139
|
+
|
|
140
|
+
USE_CN_MIRROR=1 / IM_AGENT_HUB_USE_CN=1 → npm 使用 registry.npmmirror.com
|
|
141
|
+
IM_AGENT_HUB_NO_BACKUP=1 → 更新前不备份 openclaw.json
|
|
142
|
+
IM_AGENT_HUB_UPDATE_NO_RESTART=1 → 拉取与 npm install 后不自动 start
|
|
143
|
+
IM_AGENT_HUB_REBUILD_BUNDLE=1 → 即使尚无 dist/,也执行 npm run build:bridge
|
|
144
|
+
|
|
145
|
+
若 package.json 使用 file:../dingtalk-openclaw-connector,请先同步该目录再执行 update。
|
|
146
|
+
|
|
147
|
+
若你使用 npm run build:bridge 生成 dist/dingtalk-stdio-bridge.cjs,update 后请再执行一次 npm run build:bridge 再 restart。
|
|
148
|
+
|
|
149
|
+
若本机 im-agent-hub-ctl 较旧没有 update 子命令,请在仓库内重新执行:
|
|
150
|
+
|
|
151
|
+
bash scripts/install/install-dingtalk-bridge.sh
|
|
152
|
+
|
|
153
|
+
(可 Ctrl+C 在写入配置前退出,仅用于重新安装 ctl;或手动 cp scripts/install/im-agent-hub-ctl.sh ~/.local/bin/im-agent-hub-ctl)
|
|
154
|
+
|
|
155
|
+
冲突 / 回滚
|
|
156
|
+
|
|
157
|
+
- git 冲突:在项目目录解决后再次 im-agent-hub-ctl update,或手动 npm install && im-agent-hub-ctl restart
|
|
158
|
+
- 配置被误改:用 openclaw.json.bak.* 恢复
|
|
159
|
+
|
|
160
|
+
卸载安装项(ctl + ~/.im-agent-hub,不删 Node / 不删项目 node_modules)
|
|
161
|
+
|
|
162
|
+
在 im-agent-hub 仓库内:bash scripts/install/uninstall-im-agent-hub.sh
|
|
163
|
+
EOF
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
npm_install_in_project() {
|
|
167
|
+
local root="$1"
|
|
168
|
+
cd "$root"
|
|
169
|
+
local reg=()
|
|
170
|
+
if [[ "${USE_CN_MIRROR:-0}" == "1" ]] || [[ "${IM_AGENT_HUB_USE_CN:-0}" == "1" ]]; then
|
|
171
|
+
reg=(--registry=https://registry.npmmirror.com)
|
|
172
|
+
echo "[update] npm 使用镜像 registry.npmmirror.com"
|
|
173
|
+
fi
|
|
174
|
+
npm install "${reg[@]}"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
cmd_update() {
|
|
178
|
+
load_manifest
|
|
179
|
+
local root="$IM_AGENT_HUB_PACKAGE_ROOT"
|
|
180
|
+
if [[ ! -d "$root" ]]; then
|
|
181
|
+
echo "项目目录不存在: $root (请检查 manifest 或重新运行 install-dingtalk-bridge.sh)" >&2
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
echo "[update] 项目目录: $root"
|
|
186
|
+
|
|
187
|
+
if [[ "${IM_AGENT_HUB_NO_BACKUP:-0}" != "1" ]] && [[ -f "$root/openclaw.json" ]]; then
|
|
188
|
+
local bak="$root/openclaw.json.bak.$(date +%Y%m%d%H%M%S)"
|
|
189
|
+
cp -a "$root/openclaw.json" "$bak"
|
|
190
|
+
echo "[update] 已备份 openclaw.json → $(basename "$bak")"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
cmd_stop || true
|
|
194
|
+
|
|
195
|
+
cd "$root"
|
|
196
|
+
|
|
197
|
+
if command -v git &>/dev/null && [[ -d .git ]]; then
|
|
198
|
+
echo "[update] git pull ..."
|
|
199
|
+
if ! git pull --ff-only; then
|
|
200
|
+
echo "[update] git pull --ff-only 不可用(例如需 merge),尝试 git pull ..." >&2
|
|
201
|
+
git pull || {
|
|
202
|
+
echo "[update] git pull 失败(可能有冲突),请在本目录手动处理后再: npm install && im-agent-hub-ctl start" >&2
|
|
203
|
+
exit 1
|
|
204
|
+
}
|
|
205
|
+
fi
|
|
206
|
+
else
|
|
207
|
+
echo "[update] 非 git 仓库或未安装 git,跳过 git pull,仅执行 npm install"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
npm_install_in_project "$root"
|
|
211
|
+
|
|
212
|
+
if [[ -f "$root/dist/dingtalk-stdio-bridge.cjs" ]] || [[ -f "$root/dist/dingtalk-stdio-bridge.mjs" ]] || [[ "${IM_AGENT_HUB_REBUILD_BUNDLE:-0}" == "1" ]]; then
|
|
213
|
+
if grep -q '"build:bridge"' "$root/package.json" 2>/dev/null; then
|
|
214
|
+
echo "[update] npm run build:bridge ..."
|
|
215
|
+
(cd "$root" && npm run build:bridge) || echo "[update] build:bridge 失败,将仍可用 npx tsx 启动" >&2
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
{
|
|
220
|
+
echo "LAST_UPDATED_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
221
|
+
(cd "$root" && node -p "require('./package.json').version" 2>/dev/null) | sed 's/^/PACKAGE_VERSION=/' || true
|
|
222
|
+
(cd "$root" && git rev-parse --short HEAD 2>/dev/null) | sed 's/^/GIT_REV=/' || true
|
|
223
|
+
} >"$IM_AGENT_HUB_HOME/last-update.env" 2>/dev/null || true
|
|
224
|
+
(cd "$root" && node -p "require('./package.json').version" 2>/dev/null) >"$IM_AGENT_HUB_HOME/package-version.txt" 2>/dev/null || true
|
|
225
|
+
|
|
226
|
+
if [[ "${IM_AGENT_HUB_UPDATE_NO_RESTART:-0}" == "1" ]]; then
|
|
227
|
+
echo "[update] 已完成依赖更新,未自动启动(IM_AGENT_HUB_UPDATE_NO_RESTART=1)"
|
|
228
|
+
echo "[update] 需要时执行: im-agent-hub-ctl start"
|
|
229
|
+
return 0
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
cmd_start
|
|
233
|
+
echo "[update] 完成。"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
cmd_version() {
|
|
237
|
+
load_manifest
|
|
238
|
+
local root="$IM_AGENT_HUB_PACKAGE_ROOT"
|
|
239
|
+
echo "IM_AGENT_HUB_HOME=$IM_AGENT_HUB_HOME"
|
|
240
|
+
echo "IM_AGENT_HUB_PACKAGE_ROOT=$root"
|
|
241
|
+
if [[ -f "$root/package.json" ]]; then
|
|
242
|
+
echo -n "package.json version: "
|
|
243
|
+
(cd "$root" && node -p "require('./package.json').version" 2>/dev/null) || echo "?"
|
|
244
|
+
fi
|
|
245
|
+
if command -v git &>/dev/null && [[ -d "$root/.git" ]]; then
|
|
246
|
+
echo -n "git HEAD: "
|
|
247
|
+
(cd "$root" && git rev-parse --short HEAD 2>/dev/null) || echo "?"
|
|
248
|
+
fi
|
|
249
|
+
if [[ -f "$IM_AGENT_HUB_HOME/last-update.env" ]]; then
|
|
250
|
+
echo "--- last update ---"
|
|
251
|
+
cat "$IM_AGENT_HUB_HOME/last-update.env"
|
|
252
|
+
fi
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
usage() {
|
|
256
|
+
cat <<'EOF'
|
|
257
|
+
用法: im-agent-hub-ctl <command>
|
|
258
|
+
|
|
259
|
+
start 后台启动(npx tsx scripts/dingtalk-stdio-bridge.ts)
|
|
260
|
+
stop / restart / status
|
|
261
|
+
logs 跟踪日志
|
|
262
|
+
config 编辑 openclaw.json
|
|
263
|
+
update 拉取代码 + npm install + 重启(更新主流程)
|
|
264
|
+
version 显示安装路径、包版本、git 提交、上次 update 记录
|
|
265
|
+
update-help 更新流程详细说明
|
|
266
|
+
|
|
267
|
+
环境变量: IM_AGENT_HUB_HOME(默认 ~/.im-agent-hub)
|
|
268
|
+
EOF
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "${1:-}" in
|
|
272
|
+
start) cmd_start ;;
|
|
273
|
+
stop) cmd_stop ;;
|
|
274
|
+
restart) cmd_restart ;;
|
|
275
|
+
status) cmd_status ;;
|
|
276
|
+
logs) cmd_logs "$@" ;;
|
|
277
|
+
config) cmd_config ;;
|
|
278
|
+
update) cmd_update ;;
|
|
279
|
+
version) cmd_version ;;
|
|
280
|
+
update-help) cmd_update_help ;;
|
|
281
|
+
""|-h|--help) usage ;;
|
|
282
|
+
*) echo "未知命令: $1" >&2; usage; exit 1 ;;
|
|
283
|
+
esac
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# im-agent-hub(独立仓库)钉钉桥接安装脚本
|
|
3
|
+
# 用法:在仓库根目录执行
|
|
4
|
+
# bash scripts/install/install-dingtalk-bridge.sh
|
|
5
|
+
#
|
|
6
|
+
# 已安装后的更新(不覆盖 openclaw.json,除非再次跑本脚本并确认合并):
|
|
7
|
+
# im-agent-hub-ctl update
|
|
8
|
+
# im-agent-hub-ctl update-help
|
|
9
|
+
#
|
|
10
|
+
# 可选环境变量:
|
|
11
|
+
# IM_AGENT_HUB_HOME 数据目录,默认 ~/.im-agent-hub
|
|
12
|
+
# SKIP_NODE_INSTALL 1 则只检查 Node、不自动装 nvm
|
|
13
|
+
# USE_CN_MIRROR / IM_AGENT_HUB_USE_CN 1 则 npm 使用 npmmirror
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
IM_AGENT_HUB_HOME="${IM_AGENT_HUB_HOME:-$HOME/.im-agent-hub}"
|
|
18
|
+
NODE_MIN_MAJOR="${NODE_MIN_MAJOR:-20}"
|
|
19
|
+
NODE_MIN_MINOR="${NODE_MIN_MINOR:-0}"
|
|
20
|
+
|
|
21
|
+
BOLD='\033[1m'
|
|
22
|
+
INFO='\033[36m'
|
|
23
|
+
SUCCESS='\033[32m'
|
|
24
|
+
WARN='\033[33m'
|
|
25
|
+
ERROR='\033[31m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
ui_info() { echo -e "${INFO}[INFO]${NC} $*"; }
|
|
29
|
+
ui_ok() { echo -e "${SUCCESS}[OK]${NC} $*"; }
|
|
30
|
+
ui_warn() { echo -e "${WARN}[WARN]${NC} $*"; }
|
|
31
|
+
ui_err() { echo -e "${ERROR}[ERROR]${NC} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
# scripts/install -> 仓库根目录
|
|
35
|
+
PACKAGE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
36
|
+
CTL_TEMPLATE="$SCRIPT_DIR/im-agent-hub-ctl.sh"
|
|
37
|
+
|
|
38
|
+
parse_node_major() {
|
|
39
|
+
command -v node &>/dev/null || return 1
|
|
40
|
+
node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
parse_node_minor() {
|
|
44
|
+
node -v 2>/dev/null | sed 's/^v//' | cut -d. -f2
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node_meets_minimum() {
|
|
48
|
+
command -v node &>/dev/null || return 1
|
|
49
|
+
local ma mi
|
|
50
|
+
ma="$(parse_node_major || echo 0)"
|
|
51
|
+
mi="$(parse_node_minor || echo 0)"
|
|
52
|
+
[[ "$ma" -gt "$NODE_MIN_MAJOR" ]] && return 0
|
|
53
|
+
[[ "$ma" -eq "$NODE_MIN_MAJOR" && "$mi" -ge "$NODE_MIN_MINOR" ]] && return 0
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ensure_node() {
|
|
58
|
+
if node_meets_minimum; then
|
|
59
|
+
ui_ok "Node.js $(node -v) 已满足 >= ${NODE_MIN_MAJOR}.${NODE_MIN_MINOR}"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
if [[ "${SKIP_NODE_INSTALL:-0}" == "1" ]]; then
|
|
63
|
+
ui_err "需要 Node.js >= ${NODE_MIN_MAJOR}.${NODE_MIN_MINOR},请手动安装后重试"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
ui_warn "未检测到满足版本的 Node,尝试通过 nvm + 国内镜像安装 Node ${NODE_MIN_MAJOR}..."
|
|
67
|
+
if [[ ! -f "$HOME/.nvm/nvm.sh" ]]; then
|
|
68
|
+
ui_info "正在安装 nvm..."
|
|
69
|
+
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh" | bash
|
|
70
|
+
fi
|
|
71
|
+
# shellcheck disable=SC1090
|
|
72
|
+
source "$HOME/.nvm/nvm.sh"
|
|
73
|
+
export NVM_NODEJS_ORG_MIRROR="${NVM_NODEJS_ORG_MIRROR:-https://npmmirror.com/mirrors/node}"
|
|
74
|
+
nvm install "${NODE_MIN_MAJOR}"
|
|
75
|
+
nvm alias default "${NODE_MIN_MAJOR}" >/dev/null 2>&1 || true
|
|
76
|
+
nvm use "${NODE_MIN_MAJOR}"
|
|
77
|
+
if ! node_meets_minimum; then
|
|
78
|
+
ui_err "Node 安装后仍不满足版本要求"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
ui_ok "Node.js $(node -v) 已就绪"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
prompt_required() {
|
|
85
|
+
local label="$1"
|
|
86
|
+
local var_name="$2"
|
|
87
|
+
local val=""
|
|
88
|
+
while true; do
|
|
89
|
+
read -r -p "${label}(必填): " val || true
|
|
90
|
+
val="${val//$'\r'/}"
|
|
91
|
+
val="$(sed 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$val")"
|
|
92
|
+
if [[ -n "$val" ]]; then
|
|
93
|
+
printf -v "$var_name" '%s' "$val"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
ui_warn "不能为空,请重新输入。"
|
|
97
|
+
done
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
prompt_optional() {
|
|
101
|
+
local label="$1"
|
|
102
|
+
local var_name="$2"
|
|
103
|
+
local val=""
|
|
104
|
+
read -r -p "${label}(可回车跳过): " val || true
|
|
105
|
+
printf -v "$var_name" '%s' "$val"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
normalize_base_url() {
|
|
109
|
+
local u="$1"
|
|
110
|
+
u="${u//$'\r'/}"
|
|
111
|
+
u="$(sed 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$u")"
|
|
112
|
+
u="${u%/}"
|
|
113
|
+
echo "$u"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
write_openclaw_json() {
|
|
117
|
+
export _IMH_GATEWAY_BASE="$2"
|
|
118
|
+
export _IMH_GATEWAY_TOKEN="$3"
|
|
119
|
+
export _IMH_CLIENT_ID="$4"
|
|
120
|
+
export _IMH_CLIENT_SECRET="$5"
|
|
121
|
+
export _IMH_MODEL_NAME="$6"
|
|
122
|
+
export _IMH_OUT="$1"
|
|
123
|
+
|
|
124
|
+
mkdir -p "$(dirname "$1")"
|
|
125
|
+
python3 <<'PY'
|
|
126
|
+
import copy
|
|
127
|
+
import json
|
|
128
|
+
import os
|
|
129
|
+
from urllib.parse import urlparse
|
|
130
|
+
|
|
131
|
+
gateway_base = os.environ["_IMH_GATEWAY_BASE"].strip().rstrip("/")
|
|
132
|
+
token = os.environ["_IMH_GATEWAY_TOKEN"].strip()
|
|
133
|
+
client_id = os.environ["_IMH_CLIENT_ID"].strip()
|
|
134
|
+
secret = os.environ["_IMH_CLIENT_SECRET"].strip()
|
|
135
|
+
model = os.environ.get("_IMH_MODEL_NAME", "").strip()
|
|
136
|
+
out = os.environ["_IMH_OUT"]
|
|
137
|
+
|
|
138
|
+
ding_url = gateway_base + "/api/v1/yucegpt/dingtalk/chat/"
|
|
139
|
+
|
|
140
|
+
CHANNEL_KEY = "dingtalk-connector"
|
|
141
|
+
|
|
142
|
+
cfg = {}
|
|
143
|
+
if os.path.isfile(out):
|
|
144
|
+
try:
|
|
145
|
+
with open(out, "r", encoding="utf-8") as f:
|
|
146
|
+
cfg = json.load(f)
|
|
147
|
+
except Exception:
|
|
148
|
+
cfg = {}
|
|
149
|
+
|
|
150
|
+
if not isinstance(cfg, dict):
|
|
151
|
+
cfg = {}
|
|
152
|
+
|
|
153
|
+
channels = cfg.get("channels")
|
|
154
|
+
if not isinstance(channels, dict):
|
|
155
|
+
channels = {}
|
|
156
|
+
cfg["channels"] = channels
|
|
157
|
+
|
|
158
|
+
existing = channels.get(CHANNEL_KEY)
|
|
159
|
+
if not isinstance(existing, dict):
|
|
160
|
+
existing = {}
|
|
161
|
+
|
|
162
|
+
# 在原有 channels.dingtalk-connector 上合并(保留如 gatewayPort、accounts 等未询问的字段)
|
|
163
|
+
ch = copy.deepcopy(existing)
|
|
164
|
+
ch["enabled"] = True
|
|
165
|
+
ch["clientId"] = client_id
|
|
166
|
+
ch["clientSecret"] = secret
|
|
167
|
+
ch["gatewayToken"] = token
|
|
168
|
+
ch["dingtalkAgentAccessToken"] = token
|
|
169
|
+
ch["gatewayBaseUrl"] = gateway_base
|
|
170
|
+
ch["dingtalkAgentUrl"] = ding_url
|
|
171
|
+
ch.setdefault("separateSessionByConversation", True)
|
|
172
|
+
ch.setdefault("groupSessionScope", "group")
|
|
173
|
+
ch.setdefault("asyncMode", True)
|
|
174
|
+
if model:
|
|
175
|
+
ch["modelName"] = model
|
|
176
|
+
|
|
177
|
+
# 与当前精简 openclaw.json 一致:若 URL 中带端口则同步 gatewayPort(如 http://127.0.0.1:4999)
|
|
178
|
+
try:
|
|
179
|
+
parsed = urlparse(gateway_base if "://" in gateway_base else "http://" + gateway_base)
|
|
180
|
+
if parsed.port is not None:
|
|
181
|
+
ch["gatewayPort"] = parsed.port
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
channels[CHANNEL_KEY] = ch
|
|
186
|
+
|
|
187
|
+
# 不再自动写入 bindings:项目约定 openclaw.json 可为仅含 channels 的精简结构
|
|
188
|
+
|
|
189
|
+
with open(out, "w", encoding="utf-8") as f:
|
|
190
|
+
json.dump(cfg, f, indent=2, ensure_ascii=False)
|
|
191
|
+
f.write("\n")
|
|
192
|
+
PY
|
|
193
|
+
unset _IMH_GATEWAY_BASE _IMH_GATEWAY_TOKEN _IMH_CLIENT_ID _IMH_CLIENT_SECRET _IMH_MODEL_NAME _IMH_OUT
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
npm_install_project() {
|
|
197
|
+
local root="$1"
|
|
198
|
+
cd "$root"
|
|
199
|
+
local reg=()
|
|
200
|
+
if [[ "${USE_CN_MIRROR:-0}" == "1" ]] || [[ "${IM_AGENT_HUB_USE_CN:-0}" == "1" ]]; then
|
|
201
|
+
ui_info "npm 使用镜像: registry.npmmirror.com"
|
|
202
|
+
reg=(--registry=https://registry.npmmirror.com)
|
|
203
|
+
fi
|
|
204
|
+
ui_info "在仓库根目录执行 npm install..."
|
|
205
|
+
npm install "${reg[@]}"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
write_install_manifest() {
|
|
209
|
+
mkdir -p "$IM_AGENT_HUB_HOME"
|
|
210
|
+
cat >"$IM_AGENT_HUB_HOME/manifest.env" <<EOF
|
|
211
|
+
# im-agent-hub 独立项目安装清单
|
|
212
|
+
IM_AGENT_HUB_HOME=$IM_AGENT_HUB_HOME
|
|
213
|
+
IM_AGENT_HUB_PACKAGE_ROOT=$1
|
|
214
|
+
INSTALLED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
215
|
+
EOF
|
|
216
|
+
if [[ -f "$1/package.json" ]]; then
|
|
217
|
+
(cd "$1" && node -p "require('./package.json').version" 2>/dev/null) >"$IM_AGENT_HUB_HOME/package-version.txt" || true
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
install_ctl() {
|
|
222
|
+
local bin_dir="${IM_AGENT_HUB_BIN:-$HOME/.local/bin}"
|
|
223
|
+
mkdir -p "$bin_dir"
|
|
224
|
+
if [[ ! -f "$CTL_TEMPLATE" ]]; then
|
|
225
|
+
ui_err "未找到 ctl 模板: $CTL_TEMPLATE"
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
install -m 0755 "$CTL_TEMPLATE" "$bin_dir/im-agent-hub-ctl"
|
|
229
|
+
echo "IM_AGENT_HUB_CTL_PATH=$bin_dir/im-agent-hub-ctl" >>"$IM_AGENT_HUB_HOME/manifest.env"
|
|
230
|
+
ui_ok "已安装全局命令: $bin_dir/im-agent-hub-ctl (请确保该目录在 PATH 中)"
|
|
231
|
+
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
|
|
232
|
+
ui_warn "将以下内容加入 ~/.zshrc 或 ~/.bashrc:"
|
|
233
|
+
echo " export PATH=\"$bin_dir:\$PATH\""
|
|
234
|
+
fi
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
verify_project() {
|
|
238
|
+
if [[ ! -f "$PACKAGE_ROOT/package.json" ]]; then
|
|
239
|
+
ui_err "未在 $PACKAGE_ROOT 找到 package.json,请从 im-agent-hub 仓库内运行本脚本。"
|
|
240
|
+
exit 1
|
|
241
|
+
fi
|
|
242
|
+
if [[ ! -f "$PACKAGE_ROOT/scripts/dingtalk-stdio-bridge.ts" ]]; then
|
|
243
|
+
ui_err "未找到 scripts/dingtalk-stdio-bridge.ts"
|
|
244
|
+
exit 1
|
|
245
|
+
fi
|
|
246
|
+
# package.json 中 file: 依赖需在安装 npm 前存在
|
|
247
|
+
if grep -q '"file:' "$PACKAGE_ROOT/package.json" 2>/dev/null; then
|
|
248
|
+
ui_info "检测到 file: 本地依赖,将运行 npm install(若失败请检查相对路径目录是否存在)"
|
|
249
|
+
fi
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
main() {
|
|
253
|
+
echo -e "${BOLD}im-agent-hub(独立项目)钉钉桥接安装${NC}"
|
|
254
|
+
echo ""
|
|
255
|
+
|
|
256
|
+
[[ -t 0 ]] || { ui_err "请在交互式终端中运行本脚本"; exit 1; }
|
|
257
|
+
|
|
258
|
+
verify_project
|
|
259
|
+
ui_info "项目根目录: $PACKAGE_ROOT"
|
|
260
|
+
|
|
261
|
+
ensure_node
|
|
262
|
+
|
|
263
|
+
echo ""
|
|
264
|
+
echo -e "${BOLD}钉钉插件配置(@dingtalk-real-ai/dingtalk-connector)${NC}"
|
|
265
|
+
ui_info "下面几项会写入项目根目录 ${INFO}openclaw.json${NC} 的 ${INFO}channels.dingtalk-connector${NC}。"
|
|
266
|
+
ui_info "当前仓库约定可为精简结构(仅 ${INFO}channels${NC});若文件中还有其它顶级键会原样保留。"
|
|
267
|
+
ui_info "不会自动添加 ${INFO}bindings${NC} 等字段;与 im-agent-hub 默认 openclaw.json 结构一致。"
|
|
268
|
+
echo ""
|
|
269
|
+
|
|
270
|
+
local gateway_base gateway_token client_id client_secret model_name
|
|
271
|
+
prompt_required "[钉钉插件] 服务域名 / Gateway Base URL → gatewayBaseUrl(例 https://api.example.com 或 http://127.0.0.1:4999)" gateway_base
|
|
272
|
+
prompt_required "[钉钉插件] Gateway Token → gatewayToken / dingtalkAgentAccessToken(与 yuce-gpt Python 侧一致)" gateway_token
|
|
273
|
+
prompt_required "[钉钉插件] 钉钉开放平台 Client ID → clientId(DINGTALK_CLIENT_ID)" client_id
|
|
274
|
+
prompt_required "[钉钉插件] 钉钉开放平台 Client Secret → clientSecret(DINGTALK_CLIENT_SECRET)" client_secret
|
|
275
|
+
prompt_optional "[钉钉插件] 对话模型 → modelName(可选;回车则保留原 openclaw.json 中的 modelName)" model_name
|
|
276
|
+
|
|
277
|
+
gateway_base="$(normalize_base_url "$gateway_base")"
|
|
278
|
+
|
|
279
|
+
local cfg_target="$PACKAGE_ROOT/openclaw.json"
|
|
280
|
+
ui_warn "将更新 $cfg_target 内的 channels.dingtalk-connector(钉钉插件);并自动生成 dingtalkAgentUrl"
|
|
281
|
+
read -r -p "确认继续?[y/N] " confirm || true
|
|
282
|
+
if [[ ! "${confirm:-}" =~ ^[yY]$ ]]; then
|
|
283
|
+
ui_info "已取消"
|
|
284
|
+
exit 0
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
write_openclaw_json "$cfg_target" "$gateway_base" "$gateway_token" "$client_id" "$client_secret" "$model_name"
|
|
288
|
+
ui_ok "已写入 openclaw.json → channels.dingtalk-connector(钉钉插件)"
|
|
289
|
+
|
|
290
|
+
npm_install_project "$PACKAGE_ROOT"
|
|
291
|
+
|
|
292
|
+
write_install_manifest "$PACKAGE_ROOT"
|
|
293
|
+
echo "$IM_AGENT_HUB_HOME" >"$IM_AGENT_HUB_HOME/install_home_path.txt"
|
|
294
|
+
|
|
295
|
+
install_ctl
|
|
296
|
+
|
|
297
|
+
ui_ok "安装完成。"
|
|
298
|
+
echo ""
|
|
299
|
+
ui_info "彻底卸载(不删 Node / 不删项目 node_modules 与 openclaw.json):"
|
|
300
|
+
echo " bash scripts/install/uninstall-im-agent-hub.sh"
|
|
301
|
+
echo ""
|
|
302
|
+
ui_info "常用命令:"
|
|
303
|
+
echo " im-agent-hub-ctl start | stop | restart | status | logs | config | update-help"
|
|
304
|
+
echo ""
|
|
305
|
+
ui_info "更新版本:在仓库内 git pull 后执行 npm install,再 im-agent-hub-ctl restart"
|
|
306
|
+
echo ""
|
|
307
|
+
|
|
308
|
+
read -r -p "是否现在后台启动服务?[y/N] " start_now || true
|
|
309
|
+
if [[ "${start_now:-}" =~ ^[yY]$ ]]; then
|
|
310
|
+
"${IM_AGENT_HUB_BIN:-$HOME/.local/bin}/im-agent-hub-ctl" start || true
|
|
311
|
+
fi
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
main "$@"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 复制到 ~/.config/systemd/user/im-agent-hub-dingtalk.service 后修改路径
|
|
2
|
+
# systemctl --user daemon-reload && systemctl --user enable --now im-agent-hub-dingtalk.service
|
|
3
|
+
|
|
4
|
+
[Unit]
|
|
5
|
+
Description=im-agent-hub DingTalk stdio bridge (standalone)
|
|
6
|
+
After=network-online.target
|
|
7
|
+
|
|
8
|
+
[Service]
|
|
9
|
+
Type=simple
|
|
10
|
+
# 改为你的 im-agent-hub 绝对路径
|
|
11
|
+
WorkingDirectory=%h/work/im-agent-hub
|
|
12
|
+
Environment=PATH=%h/.nvm/versions/node/v22.12.0/bin:/usr/bin:/bin
|
|
13
|
+
ExecStart=/usr/bin/env bash -lc 'cd "%h/work/im-agent-hub" && npx tsx scripts/dingtalk-stdio-bridge.ts'
|
|
14
|
+
Restart=on-failure
|
|
15
|
+
RestartSec=5
|
|
16
|
+
|
|
17
|
+
[Install]
|
|
18
|
+
WantedBy=default.target
|