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.
@@ -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