imtoagent 0.3.0 → 0.3.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.
@@ -3,6 +3,8 @@
3
3
  // ================================================================
4
4
 
5
5
  import { execSync } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
6
8
 
7
9
  export interface BackendInfo {
8
10
  type: 'claude' | 'codex' | 'opencode';
@@ -18,20 +20,64 @@ const BACKEND_DEFS: Omit<BackendInfo, 'installed' | 'version'>[] = [
18
20
  { type: 'opencode', label: 'OpenCode', installHint: 'npm install -g opencode' },
19
21
  ];
20
22
 
21
- function checkOne(b: Omit<BackendInfo, 'installed' | 'version'>): BackendInfo {
23
+ // ================================================================
24
+ // 获取 npm 全局 bin 目录
25
+ // 解决 PATH 未包含 npm global bin 时的检测失败问题
26
+ // ================================================================
27
+
28
+ let _cachedNpmBin: string | null | undefined = undefined;
29
+
30
+ function getNpmGlobalBin(): string | null {
31
+ if (_cachedNpmBin !== undefined) return _cachedNpmBin;
22
32
  try {
23
- let version: string | null = null;
24
- if (b.type === 'claude') {
25
- version = execSync('claude --version', { encoding: 'utf-8', timeout: 5000 }).trim();
26
- } else if (b.type === 'codex') {
27
- version = execSync('codex --version', { encoding: 'utf-8', timeout: 5000 }).trim();
28
- } else {
29
- version = execSync('opencode version', { encoding: 'utf-8', timeout: 5000 }).trim();
33
+ const prefix = execSync('npm get prefix', { encoding: 'utf-8', timeout: 5000 }).trim();
34
+ if (!prefix) {
35
+ _cachedNpmBin = null;
36
+ return null;
30
37
  }
38
+ const binDir = path.join(prefix, 'bin');
39
+ if (fs.existsSync(binDir)) {
40
+ _cachedNpmBin = binDir;
41
+ return binDir;
42
+ }
43
+ _cachedNpmBin = null;
44
+ return null;
45
+ } catch {
46
+ _cachedNpmBin = null;
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function checkOne(b: Omit<BackendInfo, 'installed' | 'version'>): BackendInfo {
52
+ const versionCmd: Record<string, string> = {
53
+ claude: 'claude --version',
54
+ codex: 'codex --version',
55
+ opencode: 'opencode version',
56
+ };
57
+
58
+ // 先尝试 PATH 中的命令
59
+ try {
60
+ const version = execSync(versionCmd[b.type], { encoding: 'utf-8', timeout: 5000 }).trim();
31
61
  return { ...b, installed: true, version };
32
62
  } catch {
33
- return { ...b, installed: false, version: null };
63
+ // PATH 中找不到,继续尝试 npm global bin
64
+ }
65
+
66
+ // fallback:直接从 npm global bin 目录运行
67
+ const npmBin = getNpmGlobalBin();
68
+ if (npmBin) {
69
+ const binPath = path.join(npmBin, b.type);
70
+ try {
71
+ if (fs.existsSync(binPath)) {
72
+ const version = execSync(`"${binPath}" --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
73
+ return { ...b, installed: true, version };
74
+ }
75
+ } catch {
76
+ // bin 存在但执行失败,视为未安装
77
+ }
34
78
  }
79
+
80
+ return { ...b, installed: false, version: null };
35
81
  }
36
82
 
37
83
  export function checkAllBackends(): BackendInfo[] {
@@ -75,11 +121,16 @@ export async function installBackend(
75
121
  console.log(` 命令: ${b.installHint}\n`);
76
122
 
77
123
  try {
78
- // bash -lc 加载用户 shell 环境(.bashrc/.zshrc),确保 npm 在 PATH 中
79
- const child = Bun.spawn(['bash', '-lc', b.installHint], {
124
+ // 获取 npm 全局 bin 目录,用于安装后验证(复用 getNpmGlobalBin 缓存)
125
+ const npmBinDir = getNpmGlobalBin();
126
+
127
+ // 用 zsh -ic 加载用户 shell 环境(匹配 macOS 默认 shell)
128
+ // 传入当前 PATH 环境变量,确保 npm 可执行文件可访问
129
+ const child = Bun.spawn(['zsh', '-ic', b.installHint], {
80
130
  stdout: 'pipe',
81
131
  stderr: 'pipe',
82
- stdin: 'inherit',
132
+ stdin: 'ignore',
133
+ env: { ...process.env },
83
134
  });
84
135
 
85
136
  const decoder = new TextDecoder();
@@ -104,16 +155,35 @@ export async function installBackend(
104
155
 
105
156
  if (exitCode !== 0) {
106
157
  console.error(`\n❌ ${b.label} 安装失败 (退出码: ${exitCode})`);
158
+ console.error(` 可手动运行: ${b.installHint}`);
107
159
  return false;
108
160
  }
109
161
 
110
- // 安装完成后验证
162
+ // 安装完成后验证 — 优先用 npm bin 目录直接检查
163
+ if (npmBinDir) {
164
+ const binPath = path.join(npmBinDir, b.type);
165
+ try {
166
+ if (fs.existsSync(binPath)) {
167
+ const version = execSync(`"${binPath}" --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
168
+ console.log(`\n✅ ${b.label} 安装成功! 版本: ${version}`);
169
+ return true;
170
+ }
171
+ } catch {}
172
+ }
173
+
174
+ // fallback: 通过 PATH 查找
111
175
  const info = checkOne(b);
112
176
  if (info.installed) {
113
177
  console.log(`\n✅ ${b.label} 安装成功! 版本: ${info.version}`);
114
178
  return true;
115
179
  } else {
116
- console.error(`\n❌ ${b.label} 安装后仍未检测到,请手动运行: ${b.installHint}`);
180
+ console.error(`\n❌ ${b.label} 安装后仍未检测到`);
181
+ if (npmBinDir) {
182
+ console.error(` npm 全局 bin 目录: ${npmBinDir}`);
183
+ console.error(` 建议将该目录添加到 PATH,或手动运行: ${b.installHint}`);
184
+ } else {
185
+ console.error(` 请手动运行: ${b.installHint}`);
186
+ }
117
187
  return false;
118
188
  }
119
189
  } catch (e: any) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "imtoagent",
3
- "version": "0.3.0",
4
- "description": "IM \u2194 Agent \u7edf\u4e00\u7f51\u5173 \u2014 \u98de\u4e66/Telegram/\u5fae\u4fe1/\u4f01\u4e1a\u5fae\u4fe1\u5bf9\u63a5 Claude Code/Codex/OpenCode",
3
+ "version": "0.3.1",
4
+ "description": "IM Agent 统一网关 飞书/Telegram/微信/企业微信对接 Claude Code/Codex/OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "imtoagent": "./bin/imtoagent.cjs"