@vrs-soft/wecom-aibot-mcp 2.4.25 → 3.0.0-rc.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/dist/bin.js CHANGED
@@ -13,7 +13,7 @@ import { spawn, execSync } from 'child_process';
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  import * as os from 'os';
16
- import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, getAuthToken, setAuthToken, getHttpsConfig, setHttpsConfig, updateMcpAuthHeaders, runRemoteInstallWizard, VERSION, } from './config-wizard.js';
16
+ import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, getInstalledMode, getAuthToken, setAuthToken, getHttpsConfig, setHttpsConfig, updateMcpAuthHeaders, runRemoteInstallWizard, VERSION, } from './config-wizard.js';
17
17
  import { initClient } from './client.js';
18
18
  import { registerTools } from './tools/index.js';
19
19
  import { startHttpServer, stopHttpServer, HTTP_PORT } from './http-server.js';
@@ -182,23 +182,36 @@ function isServerRunning() {
182
182
  return false;
183
183
  }
184
184
  }
185
- // 通过端口查找进程 PID(fallback,当 PID 文件不存在时)
185
+ // 通过端口查找进程 PID(fallback,当 PID 文件不存在时)。
186
+ // Windows 用 netstat -ano;Unix 优先 lsof,回退 ss。
186
187
  function findPidByPort(port) {
187
- try {
188
- // Linux: ss -tlnp | grep :18963
189
- const output = execSync(`ss -tlnp 2>/dev/null | grep ':${port}'`, { encoding: 'utf-8' });
190
- const match = output.match(/pid=(\d+)/);
191
- if (match)
192
- return parseInt(match[1]);
188
+ if (process.platform === 'win32') {
189
+ try {
190
+ const output = execSync(`netstat -ano -p TCP`, { encoding: 'utf-8' });
191
+ // 行形如: " TCP 127.0.0.1:18963 0.0.0.0:0 LISTENING 1234"
192
+ const re = new RegExp(`^\\s*TCP\\s+\\S+:${port}\\s+\\S+\\s+LISTENING\\s+(\\d+)`, 'm');
193
+ const m = output.match(re);
194
+ if (m)
195
+ return parseInt(m[1], 10);
196
+ }
197
+ catch { /* ignore */ }
198
+ return null;
193
199
  }
194
- catch { /* ignore */ }
195
200
  try {
196
- // macOS: lsof -ti :18963
201
+ // macOS / Linux 都装了 lsof
197
202
  const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf-8' }).trim();
198
203
  if (output)
199
204
  return parseInt(output.split('\n')[0]);
200
205
  }
201
206
  catch { /* ignore */ }
207
+ try {
208
+ // Linux 备选:ss -tlnp
209
+ const output = execSync(`ss -tlnp 2>/dev/null | grep ':${port}'`, { encoding: 'utf-8' });
210
+ const match = output.match(/pid=(\d+)/);
211
+ if (match)
212
+ return parseInt(match[1]);
213
+ }
214
+ catch { /* ignore */ }
202
215
  return null;
203
216
  }
204
217
  // 停止服务
@@ -373,9 +386,14 @@ function startMcpServerBackground() {
373
386
  }
374
387
  async function main() {
375
388
  const args = process.argv.slice(2);
376
- // 确定安装模式
377
- const installMode = args.includes('--http-only') ? 'http-only' :
378
- args.includes('--channel-only') ? 'channel-only' : 'full';
389
+ // 确定安装模式:优先 CLI flag,其次复用 version.json 里上次的 mode(保持 remote / channel-only 等模式不被 --upgrade 打回 full)
390
+ const explicitMode = args.includes('--http-only') ? 'http-only' :
391
+ args.includes('--channel-only') ? 'channel-only' : undefined;
392
+ const prior = getInstalledMode();
393
+ const installMode = explicitMode || prior.mode || 'full';
394
+ const remoteOptions = (installMode === 'remote' || installMode === 'remote-channel') && prior.remote?.url
395
+ ? { url: prior.remote.url, token: prior.remote.token || '' }
396
+ : undefined;
379
397
  // 以下命令跳过顶部 ensureGlobalConfigs,避免覆盖配置
380
398
  // --setup: 向导完成后自己调用
381
399
  // --channel: 作为 Channel MCP 代理运行,不应改写全局配置
@@ -390,7 +408,17 @@ async function main() {
390
408
  args.includes('--clean-cache') || args.includes('--set-token') || args.includes('--config');
391
409
  if (!skipEnsure) {
392
410
  // 强制覆盖所有全局配置(不依赖智能体)
393
- ensureGlobalConfigs(installMode);
411
+ if (installMode === 'remote' || installMode === 'remote-channel') {
412
+ if (remoteOptions) {
413
+ ensureGlobalConfigs(installMode, remoteOptions);
414
+ }
415
+ else {
416
+ console.log(`[mcp] 检测到上次安装模式 ${installMode},但缺少远程参数;跳过配置写入。如需变更请使用 --setup`);
417
+ }
418
+ }
419
+ else {
420
+ ensureGlobalConfigs(installMode);
421
+ }
394
422
  }
395
423
  // 解析命令行参数
396
424
  if (args.includes('--help') || args.includes('-h')) {
@@ -418,7 +446,13 @@ async function main() {
418
446
  const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
419
447
  const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
420
448
  const VERSION_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'version.json');
421
- const HOOK_SCRIPT = path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.sh');
449
+ const HOOK_SCRIPTS = [
450
+ path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.js'),
451
+ path.join(os.homedir(), '.wecom-aibot-mcp', 'stop-hook.js'),
452
+ // 旧版 .sh 残留
453
+ path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.sh'),
454
+ path.join(os.homedir(), '.wecom-aibot-mcp', 'stop-hook.sh'),
455
+ ];
422
456
  // 1. 删除 ~/.claude.json 中的 wecom-aibot 配置
423
457
  if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
424
458
  const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
@@ -451,10 +485,12 @@ async function main() {
451
485
  fs.unlinkSync(VERSION_FILE);
452
486
  console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/version.json');
453
487
  }
454
- // 4. 删除 hook 脚本
455
- if (fs.existsSync(HOOK_SCRIPT)) {
456
- fs.unlinkSync(HOOK_SCRIPT);
457
- console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/permission-hook.sh');
488
+ // 4. 删除 hook 脚本(含旧版 .sh)
489
+ for (const p of HOOK_SCRIPTS) {
490
+ if (fs.existsSync(p)) {
491
+ fs.unlinkSync(p);
492
+ console.log(`[mcp] 已删除 ${p}`);
493
+ }
458
494
  }
459
495
  // 5. 重新安装全局配置
460
496
  logger.log('\n[mcp] 正在重新安装...');
@@ -12,50 +12,10 @@
12
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { z } from 'zod';
15
- import { execSync } from 'child_process';
16
15
  import { VERSION, installSkill } from './config-wizard.js';
17
16
  import { addPermissionHook, registerActiveProject, unregisterActiveProject, updateWechatModeConfig } from './project-config.js';
18
17
  import { logger } from './logger.js';
19
- /**
20
- * 沿进程树向上查找 Claude Code TUI 的 PID。
21
- *
22
- * 背景:本地 dev (`command: "node"`) 时 channel-server 是 Claude TUI 的直接子进程,
23
- * process.ppid = Claude TUI ✓
24
- * 但 npx 部署 (`command: "npx"`) 时多了一层 npx:
25
- * Claude TUI → npx → node bin.js (channel-server)
26
- * process.ppid = npx ❌
27
- * permission-hook.sh 从 hook 自身向上查 active-projects.json 时只能命中 Claude TUI
28
- * 这条祖先链。如果注册的是 npx 的 PID,hook 永远找不到 → 静默 exit 0 → 跳过审批。
29
- *
30
- * 此函数从 startPid 起向上遍历,找到第一个命令名为 "claude" 的进程,返回其 PID。
31
- * 找不到时回退到 startPid(保持旧行为,至少 dev 场景不退化)。
32
- */
33
- function findClaudePid(startPid) {
34
- let pid = startPid;
35
- for (let i = 0; i < 8; i++) {
36
- if (!pid || pid <= 1)
37
- break;
38
- try {
39
- const comm = execSync(`ps -p ${pid} -o comm=`, { stdio: ['ignore', 'pipe', 'ignore'] })
40
- .toString()
41
- .trim();
42
- // ps comm= 返回执行文件 basename。Claude Code TUI 安装名就是 "claude"
43
- if (comm === 'claude' || comm.endsWith('/claude'))
44
- return pid;
45
- const ppidStr = execSync(`ps -p ${pid} -o ppid=`, { stdio: ['ignore', 'pipe', 'ignore'] })
46
- .toString()
47
- .trim();
48
- const ppid = parseInt(ppidStr, 10);
49
- if (!ppid || ppid === pid)
50
- break;
51
- pid = ppid;
52
- }
53
- catch {
54
- break;
55
- }
56
- }
57
- return startPid;
58
- }
18
+ import { findClaudePid } from './platform.js';
59
19
  const MCP_URL = process.env.MCP_URL || 'http://127.0.0.1:18963';
60
20
  const MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
61
21
  // 构建带 auth 的 fetch headers
@@ -40,7 +40,15 @@ export declare function getDocMcpUrl(robotName?: string): {
40
40
  error?: string;
41
41
  };
42
42
  export declare function ensureHookInstalled(): void;
43
- export declare function ensureGlobalConfigs(mode?: 'full' | 'http-only' | 'channel-only' | 'remote' | 'remote-channel', remoteOptions?: {
43
+ export type InstallMode = 'full' | 'http-only' | 'channel-only' | 'remote' | 'remote-channel';
44
+ export declare function getInstalledMode(): {
45
+ mode?: InstallMode;
46
+ remote?: {
47
+ url: string;
48
+ token?: string;
49
+ };
50
+ };
51
+ export declare function ensureGlobalConfigs(mode?: InstallMode, remoteOptions?: {
44
52
  url: string;
45
53
  token: string;
46
54
  }): {