217 lines
7.2 KiB
Bash
Executable File
217 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 可持续对话 · 一键安装(本地 MCP,无卡密)
|
||
# 用法:
|
||
# bash install.sh # 装全局 + 当前目录项目
|
||
# bash install.sh --global-only # 仅 ~/.cursor/mcp.json
|
||
# bash install.sh --project /path/to/repo
|
||
# bash install.sh --all-karuo # 卡若常用多根工作区一并装
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PKG_DIR="$SCRIPT_DIR/package"
|
||
INSTALL_DIR="${PCHAT_INSTALL_DIR:-$HOME/.persistent-chat-local}"
|
||
GLOBAL_MCP="$HOME/.cursor/mcp.json"
|
||
PORT="${PCHAT_HTTP_PORT:-13458}"
|
||
|
||
GLOBAL_ONLY=0
|
||
ALL_KARUO=0
|
||
PROJECTS=()
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--global-only) GLOBAL_ONLY=1; shift ;;
|
||
--all-karuo) ALL_KARUO=1; shift ;;
|
||
--project) PROJECTS+=("$2"); shift 2 ;;
|
||
*) echo "未知参数: $1"; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
if [[ "$GLOBAL_ONLY" -eq 0 && "$ALL_KARUO" -eq 0 && ${#PROJECTS[@]} -eq 0 ]]; then
|
||
PROJECTS+=("$(pwd)")
|
||
fi
|
||
|
||
if [[ "$ALL_KARUO" -eq 1 ]]; then
|
||
PROJECTS=(
|
||
"/Users/karuo/Documents/个人/卡若AI"
|
||
"/Users/karuo/Documents/开发/3、自营项目/卡若ai网站"
|
||
"/Users/karuo/Documents/开发/8、小工具/Docker项目/OpenClaw"
|
||
"/Users/karuo/Documents/开发/8、小工具/Docker项目/OpenClaw/openclaw"
|
||
"/Users/karuo/Documents/开发/7.项目调研/claude-code-sourcemap"
|
||
"/Users/karuo/Documents/开发/7.项目调研/tryvoice-oss"
|
||
)
|
||
fi
|
||
|
||
# --- 找 node ---
|
||
find_node() {
|
||
for c in \
|
||
"$(command -v node 2>/dev/null || true)" \
|
||
"/usr/local/opt/node@22/bin/node" \
|
||
"/opt/homebrew/opt/node@22/bin/node" \
|
||
"/usr/local/bin/node" \
|
||
"/opt/homebrew/bin/node"; do
|
||
[[ -n "$c" && -x "$c" ]] && echo "$c" && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
NODE_BIN="$(find_node)" || { echo "❌ 未找到 node。请先安装: brew install node@22"; exit 1; }
|
||
echo "✅ node: $NODE_BIN ($("$NODE_BIN" -v))"
|
||
|
||
# --- 部署 runtime ---
|
||
mkdir -p "$INSTALL_DIR/logs" "$INSTALL_DIR/sessions" "$INSTALL_DIR/replies"
|
||
cp "$PKG_DIR/server.js" "$INSTALL_DIR/server.js"
|
||
cp "$PKG_DIR/hub.js" "$INSTALL_DIR/hub.js"
|
||
cp "$PKG_DIR/ensure-hub.sh" "$INSTALL_DIR/ensure-hub.sh"
|
||
cp "$PKG_DIR/panel.html" "$INSTALL_DIR/panel.html"
|
||
cp "$PKG_DIR/mongo_sync.py" "$INSTALL_DIR/mongo_sync.py"
|
||
chmod +x "$INSTALL_DIR/server.js" "$INSTALL_DIR/hub.js" "$INSTALL_DIR/ensure-hub.sh" "$INSTALL_DIR/mongo_sync.py"
|
||
echo "✅ 已部署到 $INSTALL_DIR"
|
||
|
||
# --- 写全局 mcp.json(合并已有) ---
|
||
mkdir -p "$(dirname "$GLOBAL_MCP")"
|
||
TMP_MCP="$(mktemp)"
|
||
sed "s|__NODE_PATH__|$NODE_BIN|g; s|__INSTALL_DIR__|$INSTALL_DIR|g" "$PKG_DIR/templates/mcp.json" > "$TMP_MCP"
|
||
python3 <<PY
|
||
import json, os
|
||
global_mcp = os.path.expanduser("$GLOBAL_MCP")
|
||
with open("$TMP_MCP") as f:
|
||
block = json.load(f)
|
||
existing = {"mcpServers": {}}
|
||
if os.path.isfile(global_mcp):
|
||
with open(global_mcp) as f:
|
||
existing = json.load(f)
|
||
existing.setdefault("mcpServers", {})
|
||
existing["mcpServers"]["persistent-chat"] = block["mcpServers"]["persistent-chat"]
|
||
with open(global_mcp, "w") as f:
|
||
json.dump(existing, f, ensure_ascii=False, indent=2)
|
||
print("✅ 全局 MCP:", global_mcp)
|
||
PY
|
||
rm -f "$TMP_MCP"
|
||
|
||
# --- Codex config.toml(通用 · 与 Cursor 共用 Hub) ---
|
||
CODEX_CFG="$HOME/.codex/config.toml"
|
||
if [[ -f "$CODEX_CFG" ]] || [[ "${INSTALL_CODEX:-1}" == "1" ]]; then
|
||
python3 <<PY
|
||
import os, re
|
||
install = os.path.expanduser("$INSTALL_DIR")
|
||
node = "$NODE_BIN"
|
||
ws = "${PROJECTS[0]:-/Users/karuo/Documents/个人/卡若AI}"
|
||
cfg = os.path.expanduser("$CODEX_CFG")
|
||
block = f'''[mcp_servers.persistent-chat]
|
||
command = "bash"
|
||
args = ["{install}/ensure-hub.sh", "--workspace={ws}"]
|
||
|
||
[mcp_servers.persistent-chat.env]
|
||
PCHAT_HTTP_PORT = "13458"
|
||
PCHAT_NODE = "{node}"
|
||
|
||
[mcp_servers.persistent-chat.tools.wait_for_user_input]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.init_conversation]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.merge_conversation]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.select_conversation]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.verify_binding]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.verify_karuo_ai_gate]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.verify_mcp_status]
|
||
approval_mode = "auto"
|
||
|
||
[mcp_servers.persistent-chat.tools.apply_binding_choice]
|
||
approval_mode = "auto"
|
||
'''
|
||
if os.path.isfile(cfg):
|
||
with open(cfg) as f: text = f.read()
|
||
if '[mcp_servers.persistent-chat]' in text:
|
||
text = re.sub(
|
||
r'\[mcp_servers\.persistent-chat\][\s\S]*?(?=\n\[|\Z)',
|
||
block.strip() + '\n\n',
|
||
text,
|
||
count=1,
|
||
)
|
||
else:
|
||
text = text.rstrip() + '\n\n' + block
|
||
else:
|
||
os.makedirs(os.path.dirname(cfg), exist_ok=True)
|
||
text = block
|
||
with open(cfg, 'w') as f: f.write(text)
|
||
print("✅ Codex MCP:", cfg)
|
||
PY
|
||
fi
|
||
|
||
# --- 项目级:只装规则,不写 MCP(MCP 仅全局一条,避免 Settings 出现两个) ---
|
||
install_project() {
|
||
local root="$1"
|
||
[[ -d "$root" ]] || { echo "⚠️ 跳过(目录不存在): $root"; return 0; }
|
||
mkdir -p "$root/.cursor/rules"
|
||
cp "$PKG_DIR/templates/persistent-chat.mdc" "$root/.cursor/rules/persistent-chat.mdc"
|
||
# 移除项目级 persistent-chat(若曾装过)
|
||
if [[ -f "$root/.cursor/mcp.json" ]]; then
|
||
python3 -c "
|
||
import json, sys
|
||
p = sys.argv[1]
|
||
try:
|
||
with open(p) as f: d = json.load(f)
|
||
s = d.get('mcpServers') or {}
|
||
if 'persistent-chat' in s:
|
||
del s['persistent-chat']
|
||
d['mcpServers'] = s
|
||
with open(p,'w') as f: json.dump(d,f,ensure_ascii=False,indent=2); f.write('\n')
|
||
print(' 🧹 已移除项目级 MCP:', p)
|
||
except Exception: pass
|
||
" "$root/.cursor/mcp.json" 2>/dev/null || true
|
||
fi
|
||
echo "✅ 规则: $root"
|
||
}
|
||
|
||
if [[ "$GLOBAL_ONLY" -eq 0 ]]; then
|
||
for p in "${PROJECTS[@]}"; do
|
||
install_project "$p"
|
||
done
|
||
fi
|
||
|
||
# --- 启动 Hub(全局唯一 HTTP) ---
|
||
if ! curl -sf -m 2 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then
|
||
nohup "$NODE_BIN" "$INSTALL_DIR/hub.js" >> "$INSTALL_DIR/logs/hub.log" 2>&1 &
|
||
sleep 0.5
|
||
fi
|
||
if curl -sf -m 2 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then
|
||
echo "✅ Hub 面板 http://127.0.0.1:${PORT}/"
|
||
else
|
||
echo "⚠️ Hub 未启动,请手动: node $INSTALL_DIR/hub.js"
|
||
fi
|
||
|
||
# --- Mongo 同步(可选) ---
|
||
if python3 "$INSTALL_DIR/mongo_sync.py" 2>/dev/null; then
|
||
echo "✅ MongoDB 会话已同步"
|
||
else
|
||
echo "⚠️ MongoDB 同步跳过(Mongo 未开或 pymongo 未装,不影响 MCP 主功能)"
|
||
fi
|
||
|
||
# --- 验收 ---
|
||
echo ""
|
||
bash "$SCRIPT_DIR/verify.sh" || true
|
||
|
||
echo ""
|
||
bash "$SCRIPT_DIR/脚本/清理重复MCP.sh" 2>/dev/null || true
|
||
bash "$SCRIPT_DIR/check-mcp.sh" 2>/dev/null || bash "$SCRIPT_DIR/verify.sh" || true
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "🎉 安装完成(通用 · Cursor + Codex + VS Code 共用 Hub)"
|
||
echo " Cursor: ~/.cursor/mcp.json · Codex: ~/.codex/config.toml"
|
||
echo " 1. Cmd+Q 完全退出 Cursor 再打开"
|
||
echo " 2. Settings → Tools & MCP → 只保留 1 个 persistent-chat(绿点)"
|
||
echo " 3. 浏览器 http://127.0.0.1:${PORT}/ 回复 Agent"
|
||
echo " 4. Agent 模式直接说任务"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|