@aliwey/bmo 2.1.1 → 2.1.3

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/bin/bmo.js CHANGED
@@ -58,6 +58,34 @@ async function isPortOpen(port, host = '127.0.0.1') {
58
58
  });
59
59
  }
60
60
 
61
+ function killProcessByPort(port) {
62
+ try {
63
+ if (os.platform() === 'win32') {
64
+ const output = execSync(`netstat -ano`, { encoding: 'utf8' });
65
+ const lines = output.split('\n');
66
+ for (const line of lines) {
67
+ if (line.includes(`:${port}`) && line.includes('LISTENING')) {
68
+ const parts = line.trim().split(/\s+/);
69
+ const pid = parts[parts.length - 1];
70
+ if (pid && pid !== '0') {
71
+ try {
72
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
73
+ } catch (e) {}
74
+ }
75
+ }
76
+ }
77
+ } else {
78
+ try {
79
+ execSync(`fuser -k ${port}/tcp`, { stdio: 'ignore' });
80
+ } catch {
81
+ try {
82
+ execSync(`kill -9 $(lsof -t -i:${port})`, { stdio: 'ignore' });
83
+ } catch {}
84
+ }
85
+ }
86
+ } catch (e) {}
87
+ }
88
+
61
89
  /** Resolve the embedded or system Python executable */
62
90
  function getPython() {
63
91
  const embeddedWin = path.join(BMO_HOME, 'python', 'python.exe');
@@ -152,31 +180,51 @@ Data lives in: ${BMO_HOME_DISPLAY}
152
180
  // ── bmo --update [version] ────────────────────────────────────────────────
153
181
  if (cmd === '--update' || cmd === '-update' || cmd === 'update') {
154
182
  const ver = args[0] ? `@${args[0]}` : '@latest';
155
- console.log(`Stopping any running BMO background processes to release file locks...`);
183
+ console.log(`Stopping any running BMO processes and freeing ports...`);
184
+
185
+ // Kill processes on ports 3456 and 4097
186
+ killProcessByPort(3456);
187
+ killProcessByPort(4097);
188
+
156
189
  try {
157
190
  if (os.platform() === 'win32') {
158
- execSync(`powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*main.py*' -or $_.CommandLine -like '*cli.py*' -or $_.CommandLine -like '*bmo*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"`, { stdio: 'ignore' });
191
+ execSync(`powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*main.py*' -or $_.CommandLine -like '*cli.py*' -or $_.CommandLine -like '*bmo*' -or $_.CommandLine -like '*cloudflared*' -or $_.CommandLine -like '*server.js*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"`, { stdio: 'ignore' });
159
192
  } else {
160
- execSync(`pkill -f "main.py|cli.py|bmo" || true`, { stdio: 'ignore' });
193
+ execSync(`pkill -f "main.py|cli.py|bmo|cloudflared|server.js" || true`, { stdio: 'ignore' });
161
194
  }
162
195
  } catch (e) {
163
196
  // Ignore failures
164
197
  }
165
198
 
166
- console.log(`🗑️ Uninstalling old version of @aliwey/bmo...`);
167
- try {
168
- execSync('npm uninstall -g @aliwey/bmo', { stdio: 'inherit' });
169
- } catch (e) {
170
- // Ignore uninstall failures if it wasn't already installed
171
- }
172
- console.log(`🔄 Installing @aliwey/bmo${ver}...`);
173
- try {
174
- execSync(`npm install -g @aliwey/bmo${ver}`, { stdio: 'inherit' });
175
- } catch {
176
- console.error('❌ Update failed. Try manually: npm install -g @aliwey/bmo');
177
- process.exit(1);
199
+ if (os.platform() === 'win32') {
200
+ console.log('Launching updater in a new window to release folder/file locks...');
201
+
202
+ const psKillCmd = `powershell -Command \\"Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*main.py*' -or $_.CommandLine -like '*cli.py*' -or $_.CommandLine -like '*bmo*' -or $_.CommandLine -like '*cloudflared*' -or $_.CommandLine -like '*server.js*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }\\"`;
203
+ const cmdStr = `timeout /t 2 /nobreak >nul && ${psKillCmd} && echo [BMO Updater] Upgrading to @aliwey/bmo${ver}... && npm install -g @aliwey/bmo${ver} && echo [OK] BMO updated successfully! Press any key to close. && pause`;
204
+
205
+ spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/c', cmdStr], {
206
+ detached: true,
207
+ stdio: 'ignore'
208
+ });
209
+
210
+ console.log('Updater launched. This window will now close to release file locks.');
211
+ process.exit(0);
212
+ } else {
213
+ console.log(`Uninstalling old version of @aliwey/bmo...`);
214
+ try {
215
+ execSync('npm uninstall -g @aliwey/bmo', { stdio: 'inherit' });
216
+ } catch (e) {
217
+ // Ignore uninstall failures if it wasn't already installed
218
+ }
219
+ console.log(`Installing @aliwey/bmo${ver}...`);
220
+ try {
221
+ execSync(`npm install -g @aliwey/bmo${ver}`, { stdio: 'inherit' });
222
+ } catch {
223
+ console.error('Update failed. Try manually: npm install -g @aliwey/bmo');
224
+ process.exit(1);
225
+ }
226
+ process.exit(0);
178
227
  }
179
- process.exit(0);
180
228
  }
181
229
 
182
230
  // ── bmo init ──────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliwey/bmo",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "BMO — AI coding assistant with Telegram, CLI & Web sync. One command, all frontends.",
5
5
  "keywords": ["ai", "coding-assistant", "telegram-bot", "cli", "opencode", "bfp"],
6
6
  "homepage": "https://github.com/aliwey/bmo",
@@ -37,10 +37,38 @@ function getCloudflaredExe() {
37
37
  : path.join(BMO_BIN, 'cloudflared');
38
38
  if (fs.existsSync(embedded)) return embedded;
39
39
  try { execSync('cloudflared --version', { stdio: 'ignore' }); return 'cloudflared'; } catch {}
40
- console.error(' cloudflared not found. Run: bmo init');
40
+ console.error('[Error] cloudflared not found. Run: bmo init');
41
41
  process.exit(1);
42
42
  }
43
43
 
44
+ function killProcessByPort(port) {
45
+ try {
46
+ if (os.platform() === 'win32') {
47
+ const output = execSync(`netstat -ano`, { encoding: 'utf8' });
48
+ const lines = output.split('\n');
49
+ for (const line of lines) {
50
+ if (line.includes(`:${port}`) && line.includes('LISTENING')) {
51
+ const parts = line.trim().split(/\s+/);
52
+ const pid = parts[parts.length - 1];
53
+ if (pid && pid !== '0') {
54
+ try {
55
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
56
+ } catch (e) {}
57
+ }
58
+ }
59
+ }
60
+ } else {
61
+ try {
62
+ execSync(`fuser -k ${port}/tcp`, { stdio: 'ignore' });
63
+ } catch {
64
+ try {
65
+ execSync(`kill -9 $(lsof -t -i:${port})`, { stdio: 'ignore' });
66
+ } catch {}
67
+ }
68
+ }
69
+ } catch (e) {}
70
+ }
71
+
44
72
  function loadTasks() {
45
73
  try { return JSON.parse(fs.readFileSync(TASKS_FILE, 'utf8')); } catch { return {}; }
46
74
  }
@@ -70,19 +98,30 @@ function waitForTunnelUrl(proc) {
70
98
  const tasks = loadTasks();
71
99
  const existing = tasks.webchat_tunnel_url;
72
100
  if (existing) {
73
- console.log(`\n💬 Webchat already running!`);
101
+ console.log(`\n[Info] Webchat already running!`);
74
102
  console.log(` Local: http://127.0.0.1:${WEBCHAT_PORT}`);
75
103
  console.log(` Public: ${existing}\n`);
76
104
  process.exit(0);
105
+ } else {
106
+ console.log(`[Info] Port ${WEBCHAT_PORT} is in use by an unrecognized process. Freeing port...`);
107
+ killProcessByPort(WEBCHAT_PORT);
108
+ await sleep(1000); // Wait for the port to release
77
109
  }
78
110
  }
79
111
 
80
112
  // Start webchat server
81
- console.log('Starting webchat server...');
113
+ console.log('Starting webchat server...');
82
114
  const webchatDir = path.join(PKG_DIR, 'webchat');
115
+
116
+ // Ensure logs directory exists
117
+ const logDir = path.join(BMO_HOME, 'logs');
118
+ fs.mkdirSync(logDir, { recursive: true });
119
+ const webchatLogPath = path.join(logDir, 'webchat.log');
120
+ const logStream = fs.openSync(webchatLogPath, 'a');
121
+
83
122
  const webchat = spawn('node', ['server.js'], {
84
123
  cwd: webchatDir,
85
- stdio: 'ignore',
124
+ stdio: ['ignore', logStream, logStream],
86
125
  detached: true,
87
126
  env: {
88
127
  ...process.env,
@@ -94,15 +133,32 @@ function waitForTunnelUrl(proc) {
94
133
  webchat.unref();
95
134
 
96
135
  // Wait for webchat to be ready
136
+ let started = false;
97
137
  for (let i = 0; i < 20; i++) {
98
138
  await sleep(500);
99
- if (await isPortOpen(WEBCHAT_PORT)) break;
100
- if (i === 19) { console.error('❌ Webchat failed to start'); process.exit(1); }
139
+ if (await isPortOpen(WEBCHAT_PORT)) {
140
+ started = true;
141
+ break;
142
+ }
143
+ }
144
+
145
+ if (!started) {
146
+ console.error('[Error] Webchat failed to start');
147
+ try {
148
+ const logContent = fs.readFileSync(webchatLogPath, 'utf8').trim();
149
+ const lines = logContent.split('\n');
150
+ const lastLines = lines.slice(-15).join('\n');
151
+ console.error('\nLast log output:');
152
+ console.error(lastLines);
153
+ } catch (e) {
154
+ console.error('Could not read webchat log file.');
155
+ }
156
+ process.exit(1);
101
157
  }
102
- console.log(' Webchat server running on port', WEBCHAT_PORT);
158
+ console.log('[OK] Webchat server running on port', WEBCHAT_PORT);
103
159
 
104
160
  // Start cloudflared tunnel
105
- console.log('Starting cloudflared tunnel...');
161
+ console.log('Starting cloudflared tunnel...');
106
162
  const cfExe = getCloudflaredExe();
107
163
  const cf = spawn(cfExe, ['tunnel', '--url', `http://localhost:${WEBCHAT_PORT}`], {
108
164
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -114,7 +170,7 @@ function waitForTunnelUrl(proc) {
114
170
  try {
115
171
  tunnelUrl = await waitForTunnelUrl(cf);
116
172
  } catch {
117
- console.error(' Could not get tunnel URL from cloudflared');
173
+ console.error('[Error] Could not get tunnel URL from cloudflared');
118
174
  process.exit(1);
119
175
  }
120
176
 
@@ -127,7 +183,7 @@ function waitForTunnelUrl(proc) {
127
183
  saveTasks(tasks);
128
184
 
129
185
  console.log('\n╭────────────────────────────────────────────╮');
130
- console.log('│ 💬 BMO Webchat is live! │');
186
+ console.log('│ [OK] BMO Webchat is live! │');
131
187
  console.log(`│ Local: http://127.0.0.1:${WEBCHAT_PORT} │`);
132
188
  console.log(`│ Public: ${tunnelUrl.padEnd(34)} │`);
133
189
  console.log('╰────────────────────────────────────────────╯\n');