claude-opencode-viewer 2.6.22 → 2.6.24

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +49 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.6.22",
3
+ "version": "2.6.24",
4
4
  "description": "A unified terminal viewer for Claude Code and OpenCode with seamless switching",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -19,6 +19,7 @@ const IS_PC = process.argv.includes('--pc');
19
19
  const PORT = parseInt(process.argv[2]) || 7008;
20
20
  const USE_HTTPS = process.argv.includes('--https');
21
21
  const JSON_OUTPUT = process.argv.includes('--json');
22
+ const LOG = JSON_OUTPUT ? console.log.bind(console) : () => {};
22
23
 
23
24
  // 自签名证书生成(使用 selfsigned 库,不依赖 openssl)
24
25
  async function getOrCreateCert() {
@@ -30,7 +31,7 @@ async function getOrCreateCert() {
30
31
  return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
31
32
  }
32
33
 
33
- if (!JSON_OUTPUT) console.log('🔐 首次使用 HTTPS,生成自签名证书...');
34
+ if (!JSON_OUTPUT) LOG('🔐 首次使用 HTTPS,生成自签名证书...');
34
35
  mkdirSync(certDir, { recursive: true });
35
36
 
36
37
  const { default: selfsigned } = await import('selfsigned');
@@ -48,7 +49,7 @@ async function getOrCreateCert() {
48
49
 
49
50
  writeFileSync(keyPath, pems.private);
50
51
  writeFileSync(certPath, pems.cert);
51
- if (!JSON_OUTPUT) console.log(`📁 证书已保存到 ${certDir}`);
52
+ if (!JSON_OUTPUT) LOG(`📁 证书已保存到 ${certDir}`);
52
53
  return { key: pems.private, cert: pems.cert };
53
54
  }
54
55
 
@@ -200,7 +201,7 @@ async function spawnProcess(mode, sessionId = null) {
200
201
  // 如果提供了 sessionId,添加 --session 参数
201
202
  if (sessionId) {
202
203
  args = ['--session', sessionId];
203
- console.log(`[opencode] 恢复会话: ${sessionId}`);
204
+ LOG(`[opencode] 恢复会话: ${sessionId}`);
204
205
  }
205
206
  }
206
207
 
@@ -219,10 +220,10 @@ async function spawnProcess(mode, sessionId = null) {
219
220
  db.close();
220
221
  if (session && session.directory && existsSync(session.directory)) {
221
222
  spawnCwd = session.directory;
222
- console.log(`[opencode] 使用会话目录: ${spawnCwd}`);
223
+ LOG(`[opencode] 使用会话目录: ${spawnCwd}`);
223
224
  }
224
225
  } catch (e) {
225
- console.log('[opencode] 查询会话目录失败:', e.message);
226
+ LOG('[opencode] 查询会话目录失败:', e.message);
226
227
  }
227
228
  }
228
229
 
@@ -245,7 +246,7 @@ async function spawnProcess(mode, sessionId = null) {
245
246
  });
246
247
 
247
248
  proc.onExit(({ exitCode }) => {
248
- console.log(`[onExit] 进程退出, PID: ${proc.pid}, exitCode: ${exitCode}`);
249
+ LOG(`[onExit] 进程退出, PID: ${proc.pid}, exitCode: ${exitCode}`);
249
250
  if (currentProcess === proc) {
250
251
  currentProcess = null;
251
252
  }
@@ -264,7 +265,7 @@ async function spawnProcess(mode, sessionId = null) {
264
265
  if (mode === 'claude') {
265
266
  if (claudeProcess && claudeProcess !== proc && claudeProcess.pid) {
266
267
  try {
267
- console.log(`[spawnProcess] 清理旧 claude 进程 PID: ${claudeProcess.pid}`);
268
+ LOG(`[spawnProcess] 清理旧 claude 进程 PID: ${claudeProcess.pid}`);
268
269
  killProcessTree(claudeProcess);
269
270
  } catch {}
270
271
  }
@@ -272,7 +273,7 @@ async function spawnProcess(mode, sessionId = null) {
272
273
  } else {
273
274
  if (opencodeProcess && opencodeProcess !== proc && opencodeProcess.pid) {
274
275
  try {
275
- console.log(`[spawnProcess] 清理旧 opencode 进程 PID: ${opencodeProcess.pid}`);
276
+ LOG(`[spawnProcess] 清理旧 opencode 进程 PID: ${opencodeProcess.pid}`);
276
277
  killProcessTree(opencodeProcess);
277
278
  } catch {}
278
279
  }
@@ -281,7 +282,7 @@ async function spawnProcess(mode, sessionId = null) {
281
282
  // 追踪当前会话 ID
282
283
  if (sessionId) {
283
284
  currentSessionId = sessionId;
284
- console.log(`[session] 当前会话 ID: ${currentSessionId}`);
285
+ LOG(`[session] 当前会话 ID: ${currentSessionId}`);
285
286
  } else {
286
287
  // 新建会话:保持 null,前端点击复制时不显示内容
287
288
  currentSessionId = null;
@@ -289,14 +290,14 @@ async function spawnProcess(mode, sessionId = null) {
289
290
  }
290
291
 
291
292
  currentProcess = proc;
292
- console.log(`[claude-opencode-viewer] ${mode === 'claude' ? 'Claude Code' : 'OpenCode'} 已启动 (PID: ${proc.pid})`);
293
+ LOG(`[claude-opencode-viewer] ${mode === 'claude' ? 'Claude Code' : 'OpenCode'} 已启动 (PID: ${proc.pid})`);
293
294
  return proc;
294
295
  }
295
296
 
296
297
  async function switchMode(newMode) {
297
298
  if (newMode === currentMode) return;
298
299
 
299
- console.log(`[switchMode] 从 ${currentMode} 切换到 ${newMode}`);
300
+ LOG(`[switchMode] 从 ${currentMode} 切换到 ${newMode}`);
300
301
 
301
302
  isSwitching = true;
302
303
 
@@ -306,18 +307,18 @@ async function switchMode(newMode) {
306
307
  // 杀死旧进程
307
308
  if (currentMode === 'claude' && claudeProcess) {
308
309
  try {
309
- console.log(`[switchMode] 杀死 claude 进程 PID: ${claudeProcess.pid}`);
310
+ LOG(`[switchMode] 杀死 claude 进程 PID: ${claudeProcess.pid}`);
310
311
  killProcessTree(claudeProcess);
311
312
  } catch (e) {
312
- console.log('[switchMode] 杀死 claude 进程失败:', e.message);
313
+ LOG('[switchMode] 杀死 claude 进程失败:', e.message);
313
314
  }
314
315
  claudeProcess = null;
315
316
  } else if (currentMode === 'opencode' && opencodeProcess) {
316
317
  try {
317
- console.log(`[switchMode] 杀死 opencode 进程 PID: ${opencodeProcess.pid}`);
318
+ LOG(`[switchMode] 杀死 opencode 进程 PID: ${opencodeProcess.pid}`);
318
319
  killProcessTree(opencodeProcess);
319
320
  } catch (e) {
320
- console.log('[switchMode] 杀死 opencode 进程失败:', e.message);
321
+ LOG('[switchMode] 杀死 opencode 进程失败:', e.message);
321
322
  }
322
323
  opencodeProcess = null;
323
324
  }
@@ -334,9 +335,9 @@ async function switchMode(newMode) {
334
335
  if (currentProcess && lastPtyCols && lastPtyRows) {
335
336
  try { currentProcess.resize(lastPtyCols, lastPtyRows); } catch {}
336
337
  }
337
- console.log(`[switchMode] 切换到 ${newMode} 成功`);
338
+ LOG(`[switchMode] 切换到 ${newMode} 成功`);
338
339
  } catch (e) {
339
- console.error('[switchMode] 启动新进程失败:', e.message);
340
+ LOG('[switchMode] 启动新进程失败:', e.message);
340
341
  }
341
342
  isSwitching = false;
342
343
  }
@@ -361,7 +362,7 @@ function resizePty(cols, rows) {
361
362
  function getOpenCodeSessions() {
362
363
  try {
363
364
  if (!existsSync(OPENCODE_DB_PATH)) {
364
- console.log('[DB] OpenCode 数据库不存在:', OPENCODE_DB_PATH);
365
+ LOG('[DB] OpenCode 数据库不存在:', OPENCODE_DB_PATH);
365
366
  return [];
366
367
  }
367
368
 
@@ -416,7 +417,7 @@ function getOpenCodeSessions() {
416
417
  : partData.text;
417
418
  }
418
419
  } catch (e) {
419
- console.error('[DB] 解析 part 失败:', e.message);
420
+ LOG('[DB] 解析 part 失败:', e.message);
420
421
  }
421
422
  }
422
423
  }
@@ -430,7 +431,7 @@ function getOpenCodeSessions() {
430
431
  db.close();
431
432
  return result;
432
433
  } catch (err) {
433
- console.error('[DB] 读取会话失败:', err.message);
434
+ LOG('[DB] 读取会话失败:', err.message);
434
435
  return [];
435
436
  }
436
437
  }
@@ -501,7 +502,7 @@ function getSessionMessages(sessionId) {
501
502
  db.close();
502
503
  return result;
503
504
  } catch (err) {
504
- console.error('[DB] 读取消息失败:', err.message);
505
+ LOG('[DB] 读取消息失败:', err.message);
505
506
  return [];
506
507
  }
507
508
  }
@@ -661,7 +662,7 @@ const requestHandler = async (req, res) => {
661
662
  db.close();
662
663
  res.end(JSON.stringify({ ok: true, changes: result.changes }));
663
664
  } catch (err) {
664
- console.error('[DB] 软删除会话失败:', err.message);
665
+ LOG('[DB] 软删除会话失败:', err.message);
665
666
  res.end(JSON.stringify({ ok: false, error: err.message }));
666
667
  }
667
668
  return;
@@ -706,7 +707,7 @@ function createServerAndWss() {
706
707
 
707
708
  function setupWss(wssInst) {
708
709
  wssInst.on('connection', (ws, req) => {
709
- console.log('[WS] 客户端连接 from', req.socket.remoteAddress);
710
+ LOG('[WS] 客户端连接 from', req.socket.remoteAddress);
710
711
 
711
712
  ws.send(JSON.stringify({
712
713
  type: 'state',
@@ -732,7 +733,7 @@ wssInst.on('connection', (ws, req) => {
732
733
  await spawnProcess('opencode', sid);
733
734
  ws.send(JSON.stringify({ type: 'started', sessionId: sid }));
734
735
  } catch (e) {
735
- console.error('[reconnect] 重启失败:', e.message);
736
+ LOG('[reconnect] 重启失败:', e.message);
736
737
  }
737
738
  isSwitching = false;
738
739
  }, 200);
@@ -758,19 +759,19 @@ wssInst.on('connection', (ws, req) => {
758
759
  ws.on('message', async (raw) => {
759
760
  try {
760
761
  const msg = JSON.parse(raw);
761
- console.log(`[WS msg] type=${msg.type}, currentProcess=${!!currentProcess}, currentMode=${currentMode}`);
762
+ LOG(`[WS msg] type=${msg.type}, currentProcess=${!!currentProcess}, currentMode=${currentMode}`);
762
763
 
763
764
  if (msg.type === 'input') {
764
765
  // 进程已退出时,自动重新启动(参考 cc-viewer 逻辑)
765
766
  // 模式切换/新建会话期间不触发 respawn
766
767
  if (!currentProcess && !isSwitching) {
767
768
  try {
768
- console.log(`[respawn] 进程已退出,自动重新启动 ${currentMode}`);
769
+ LOG(`[respawn] 进程已退出,自动重新启动 ${currentMode}`);
769
770
  outputBuffer = '';
770
771
  await spawnProcess(currentMode);
771
- console.log(`[respawn] 重新启动成功, currentProcess=${!!currentProcess}`);
772
+ LOG(`[respawn] 重新启动成功, currentProcess=${!!currentProcess}`);
772
773
  } catch (e) {
773
- console.log(`[respawn] 重新启动失败: ${e.message}`);
774
+ LOG(`[respawn] 重新启动失败: ${e.message}`);
774
775
  }
775
776
  }
776
777
  if (activeWs !== ws) {
@@ -809,17 +810,17 @@ wssInst.on('connection', (ws, req) => {
809
810
  } else if (msg.type === 'restore') {
810
811
  // 恢复会话
811
812
  if (msg.sessionId && currentMode === 'opencode') {
812
- console.log(`[restore] 恢复会话: ${msg.sessionId}`);
813
+ LOG(`[restore] 恢复会话: ${msg.sessionId}`);
813
814
 
814
815
  isSwitching = true;
815
816
 
816
817
  // 杀死当前 opencode 进程
817
818
  if (opencodeProcess) {
818
819
  try {
819
- console.log(`[restore] 杀死当前进程 PID: ${opencodeProcess.pid}`);
820
+ LOG(`[restore] 杀死当前进程 PID: ${opencodeProcess.pid}`);
820
821
  killProcessTree(opencodeProcess);
821
822
  } catch (e) {
822
- console.log('[restore] 杀死进程失败:', e.message);
823
+ LOG('[restore] 杀死进程失败:', e.message);
823
824
  }
824
825
  opencodeProcess = null;
825
826
  currentProcess = null;
@@ -836,7 +837,7 @@ wssInst.on('connection', (ws, req) => {
836
837
  await spawnProcess('opencode', msg.sessionId);
837
838
  ws.send(JSON.stringify({ type: 'restored', sessionId: msg.sessionId }));
838
839
  } catch (e) {
839
- console.error('[restore] 启动进程失败:', e.message);
840
+ LOG('[restore] 启动进程失败:', e.message);
840
841
  ws.send(JSON.stringify({ type: 'restore-error', error: e.message }));
841
842
  }
842
843
  isSwitching = false;
@@ -844,19 +845,19 @@ wssInst.on('connection', (ws, req) => {
844
845
  } else if (msg.type === 'start') {
845
846
  // 前端启动指令:可选带 sessionId 恢复会话
846
847
  const mode = currentMode;
847
- console.log(`[start] 启动 ${mode}, sessionId=${msg.sessionId || '(新会话)'}`);
848
+ LOG(`[start] 启动 ${mode}, sessionId=${msg.sessionId || '(新会话)'}`);
848
849
  outputBuffer = '';
849
850
  try {
850
851
  await spawnProcess(mode, msg.sessionId || null);
851
852
  ws.send(JSON.stringify({ type: 'started', sessionId: msg.sessionId || null }));
852
853
  } catch (e) {
853
- console.error('[start] 启动失败:', e.message);
854
+ LOG('[start] 启动失败:', e.message);
854
855
  ws.send(JSON.stringify({ type: 'start-error', error: e.message }));
855
856
  }
856
857
  } else if (msg.type === 'new-session') {
857
858
  // 开启新会话:杀掉当前进程,重新启动不带 session 参数的 opencode
858
859
  const mode = currentMode;
859
- console.log(`[new-session] 开启新会话, mode=${mode}`);
860
+ LOG(`[new-session] 开启新会话, mode=${mode}`);
860
861
 
861
862
  isSwitching = true;
862
863
  previousSessionId = currentSessionId; // 记住旧会话,用于判断新会话是否已写入 DB
@@ -880,7 +881,7 @@ wssInst.on('connection', (ws, req) => {
880
881
  try {
881
882
  await spawnProcess(mode);
882
883
  } catch (e) {
883
- console.error('[new-session] 启动失败:', e.message);
884
+ LOG('[new-session] 启动失败:', e.message);
884
885
  ws.send(JSON.stringify({ type: 'new-session-error', error: e.message }));
885
886
  }
886
887
  isSwitching = false;
@@ -898,7 +899,7 @@ wssInst.on('connection', (ws, req) => {
898
899
  db.close();
899
900
  if (row && row.id !== prevSid) {
900
901
  currentSessionId = row.id;
901
- console.log(`[new-session] 检测到新会话 ID: ${currentSessionId}`);
902
+ LOG(`[new-session] 检测到新会话 ID: ${currentSessionId}`);
902
903
  return;
903
904
  }
904
905
  }
@@ -908,7 +909,7 @@ wssInst.on('connection', (ws, req) => {
908
909
  setTimeout(() => checkNewSession(0), 3000);
909
910
  }
910
911
  } catch (err) {
911
- console.error('[WS] Error:', err.message);
912
+ LOG('[WS] Error:', err.message);
912
913
  }
913
914
  });
914
915
 
@@ -954,16 +955,16 @@ function startServer() {
954
955
  const proto = USE_HTTPS ? 'https' : 'http';
955
956
 
956
957
  if (JSON_OUTPUT) {
957
- console.log(JSON.stringify({ port: PORT, url: `${proto}://127.0.0.1:${PORT}`, ip, proto, pid: process.pid }));
958
+ LOG(JSON.stringify({ port: PORT, url: `${proto}://127.0.0.1:${PORT}`, ip, proto, pid: process.pid }));
958
959
  } else {
959
- console.log('\n' + '='.repeat(50));
960
- console.log('✅ Claude OpenCode Viewer 已启动');
961
- console.log('='.repeat(50));
962
- console.log(`🖥️ 本地访问:${proto}://127.0.0.1:${PORT}`);
963
- console.log(`📱 手机访问:${proto}://${ip}:${PORT}`);
964
- if (USE_HTTPS) console.log('🔐 HTTPS 模式(首次访问需信任自签名证书)');
965
- console.log('='.repeat(50));
966
- console.log('\n按 Ctrl+C 停止服务\n');
960
+ LOG('\n' + '='.repeat(50));
961
+ LOG('✅ Claude OpenCode Viewer 已启动');
962
+ LOG('='.repeat(50));
963
+ LOG(`🖥️ 本地访问:${proto}://127.0.0.1:${PORT}`);
964
+ LOG(`📱 手机访问:${proto}://${ip}:${PORT}`);
965
+ if (USE_HTTPS) LOG('🔐 HTTPS 模式(首次访问需信任自签名证书)');
966
+ LOG('='.repeat(50));
967
+ LOG('\n按 Ctrl+C 停止服务\n');
967
968
  }
968
969
 
969
970
  // 尝试恢复最近的会话,如果没有则新建
@@ -978,7 +979,7 @@ function startServer() {
978
979
  } catch (e) {}
979
980
 
980
981
  if (lastSessionId) {
981
- console.log(`[startup] 恢复最近会话: ${lastSessionId}`);
982
+ LOG(`[startup] 恢复最近会话: ${lastSessionId}`);
982
983
  await spawnProcess('opencode', lastSessionId);
983
984
  } else {
984
985
  await spawnProcess('opencode');
@@ -988,7 +989,6 @@ function startServer() {
988
989
  server.on('error', (err) => {
989
990
  console.error(`启动失败: ${err.message}`);
990
991
  process.exit(1);
991
- }
992
992
  });
993
993
  }
994
994