diff --git a/scripts/apply-tang-cursor-config-103.mjs b/scripts/apply-tang-cursor-config-103.mjs index 7d620ac..6ff0c2b 100644 --- a/scripts/apply-tang-cursor-config-103.mjs +++ b/scripts/apply-tang-cursor-config-103.mjs @@ -6,6 +6,7 @@ * Usage: * node scripts/apply-tang-cursor-config-103.mjs * node scripts/apply-tang-cursor-config-103.mjs --apply + * node scripts/apply-tang-cursor-config-103.mjs --apply --enable-chat-bridge */ import process from 'node:process'; import { execSync } from 'node:child_process'; @@ -38,6 +39,7 @@ RECOMMENDED.intentAllowlist = intentAllowlistFromFeatures(RECOMMENDED.features); async function main() { const apply = process.argv.includes('--apply'); + const enableChatBridge = process.argv.includes('--enable-chat-bridge'); const databaseUrl = execSync( "ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \"grep '^DATABASE_URL=' /Users/john/Project/Memind/.env | cut -d= -f2-\"", { encoding: 'utf8' }, @@ -49,10 +51,24 @@ async function main() { 'SELECT config_json FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1', ['global'], ); + const target = { + ...RECOMMENDED, + features: { + ...RECOMMENDED.features, + ...(enableChatBridge + ? { [CURSOR_CHANNEL_FEATURES.CHAT_BRIDGE]: { enabled: true } } + : {}), + }, + }; + target.intentAllowlist = intentAllowlistFromFeatures(target.features); + if (enableChatBridge) { + target.meta = { ...target.meta, notes: '唐用户 Cursor 灰度 — 页面+问卷+定时任务+聊天 Bridge' }; + } + console.log('--- before ---'); console.log(JSON.stringify(before[0]?.config_json ?? null, null, 2)); console.log('--- target ---'); - console.log(JSON.stringify(RECOMMENDED, null, 2)); + console.log(JSON.stringify(target, null, 2)); if (!apply) { console.log('\n(dry-run) 追加 --apply 写入生产库'); @@ -67,7 +83,7 @@ async function main() { ON DUPLICATE KEY UPDATE config_json = VALUES(config_json), updated_at = VALUES(updated_at)`, - [JSON.stringify(RECOMMENDED), now], + [JSON.stringify(target), now], ); const [after] = await pool.query( diff --git a/scripts/build-portal-runtime.mjs b/scripts/build-portal-runtime.mjs index 8aa7712..80f2db2 100755 --- a/scripts/build-portal-runtime.mjs +++ b/scripts/build-portal-runtime.mjs @@ -176,6 +176,26 @@ async function bundleDeepseekCompatProxy() { await run(esbuildBin, args); } +async function bundleCursorChatBridge() { + if (!(await exists(esbuildBin))) { + throw new Error(`未找到 esbuild: ${esbuildBin}`); + } + console.log('==> 打包 Cursor Chat Bridge'); + const args = [ + 'cursor-openai-bridge.mjs', + '--bundle', + '--platform=node', + '--format=esm', + `--target=${runtimeNodeTarget}`, + '--outfile=.runtime/portal/cursor-openai-bridge.mjs', + '--banner:js=import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);', + ]; + for (const pkg of externalPackages) { + args.push(`--external:${pkg}`); + } + await run(esbuildBin, args); +} + async function bundleSandboxMcp() { if (!(await exists(esbuildBin))) { throw new Error(`未找到 esbuild: ${esbuildBin}`); @@ -430,6 +450,7 @@ async function writeMetadata() { 'run-memind-portal-candidate.sh', 'run-memind-canary-proxy-prod.sh', 'run-deepseek-compat-proxy-candidate.sh', + 'run-cursor-chat-bridge-prod.sh', ]) { const source = path.join(root, 'scripts', scriptName); if (!(await exists(source))) { @@ -523,6 +544,10 @@ async function writeMetadata() { path.join(root, 'scripts', 'install-agent-run-guard-agent.sh'), path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'), ); + await fs.copyFile( + path.join(root, 'scripts', 'install-cursor-chat-bridge-agent.sh'), + path.join(runtimeRoot, 'scripts', 'install-cursor-chat-bridge-agent.sh'), + ); await fs.copyFile( path.join(root, 'scripts', 'resume-agent-run-prod.sh'), path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'), @@ -553,6 +578,7 @@ async function writeMetadata() { ' wechat-mp.bundle.mjs (esbuild bundle; hot-swappable WeChat MP module)', ' memind-canary-proxy.mjs (immutable-identity stable/candidate router)', ' deepseek-no-think-proxy.mjs (DeepSeek V4 tool-round compatibility proxy)', + ' cursor-openai-bridge.mjs (Cursor agent OpenAI-compatible chat bridge)', '', 'Post-deploy validation (scripts/check-mindspace-public-links.mjs):', ' Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.', @@ -625,6 +651,7 @@ async function main() { await bundleServer(); await bundleCanaryProxy(); await bundleDeepseekCompatProxy(); + await bundleCursorChatBridge(); await bundleWechatMp(); await bundleSandboxMcp(); await bundleMindSearchMcp(); @@ -642,6 +669,7 @@ async function main() { path.join(runtimeRoot, 'scripts', 'run-deepseek-compat-proxy-candidate.sh'), 0o755, ); + await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-cursor-chat-bridge-prod.sh'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'wechat-mp-menu.mjs'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-stream-runtime.mjs'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-drain.mjs'), 0o755); @@ -656,6 +684,7 @@ async function main() { await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-worker-agent.sh'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'agent-run-guard.mjs'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-agent-run-guard-agent.sh'), 0o755); + await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-cursor-chat-bridge-agent.sh'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'resume-agent-run-prod.sh'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'ensure-goose-session-postgres.sh'), 0o755); await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-tool-runtime.mjs'), 0o755); diff --git a/scripts/install-cursor-chat-bridge-agent.sh b/scripts/install-cursor-chat-bridge-agent.sh new file mode 100755 index 0000000..3c24486 --- /dev/null +++ b/scripts/install-cursor-chat-bridge-agent.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" +RUNNER="${ROOT}/scripts/run-cursor-chat-bridge-prod.sh" +LABEL="${MEMIND_CURSOR_CHAT_BRIDGE_LABEL:-cn.tkmind.cursor-chat-bridge}" +PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist" +LOG="$HOME/Library/Logs/memind-cursor-chat-bridge.log" +GUI="gui/$(id -u)" +PORT="${MEMIND_CURSOR_CHAT_BRIDGE_PORT:-18040}" + +mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs" + +if [[ ! -x "$NODE_BIN" ]]; then + NODE_BIN="$(command -v node)" +fi +if [[ ! -f "$RUNNER" ]]; then + echo "cursor chat bridge runner not found: $RUNNER" >&2 + exit 1 +fi +chmod +x "$RUNNER" + +cat > "$PLIST" < + + + + Label + $LABEL + ProgramArguments + + /bin/bash + $RUNNER + + WorkingDirectory + $ROOT + RunAtLoad + + KeepAlive + + ThrottleInterval + 10 + StandardOutPath + $LOG + StandardErrorPath + $LOG + EnvironmentVariables + + PATH + /opt/homebrew/bin:/opt/homebrew/opt/node@24/bin:/usr/local/bin:/usr/bin:/bin + MEMIND_CURSOR_CHAT_BRIDGE_ENABLED + 1 + MEMIND_CURSOR_CHAT_BRIDGE_ENTRYPOINT + 1 + MEMIND_CURSOR_CHAT_BRIDGE_PORT + $PORT + + + +EOF + +plutil -lint "$PLIST" +launchctl bootout "$GUI/$LABEL" 2>/dev/null || true +launchctl bootstrap "$GUI" "$PLIST" +launchctl enable "$GUI/$LABEL" +launchctl kickstart -k "$GUI/$LABEL" + +echo "installed $PLIST" +echo "runner: $RUNNER" +echo "port: $PORT" +echo "log: $LOG" diff --git a/scripts/run-cursor-chat-bridge-prod.sh b/scripts/run-cursor-chat-bridge-prod.sh new file mode 100755 index 0000000..a346092 --- /dev/null +++ b/scripts/run-cursor-chat-bridge-prod.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +if [[ ! -f "${ROOT}/.env" ]]; then + echo "[cursor-chat-bridge] missing .env in ${ROOT}" >&2 + exit 1 +fi + +cd "${ROOT}" + +set -a +# shellcheck disable=SC1091 +source "${ROOT}/.env" +set +a + +export NODE_ENV=production +export MEMIND_CURSOR_CHAT_BRIDGE_ENTRYPOINT=1 +export MEMIND_CURSOR_CHAT_BRIDGE_PORT="${MEMIND_CURSOR_CHAT_BRIDGE_PORT:-18040}" + +NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}" +if [[ ! -x "${NODE_BIN}" ]]; then + NODE_BIN="$(command -v node)" +fi + +BRIDGE="${ROOT}/cursor-openai-bridge.mjs" +if [[ ! -f "${BRIDGE}" ]]; then + echo "[cursor-chat-bridge] bundle not found: ${BRIDGE}" >&2 + exit 1 +fi + +exec "${NODE_BIN}" "${BRIDGE}"