claude-opencode-viewer 2.6.5 → 2.6.6

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/cov.js CHANGED
@@ -42,6 +42,7 @@ if (args.includes('--help') || args.includes('-h')) {
42
42
  ║ 用法: ║
43
43
  ║ cov 在当前目录启动服务(端口7008) ║
44
44
  ║ cov --pc PC端随机端口启动(19200-19220) ║
45
+ ║ cov --pc --https PC端HTTPS模式启动 ║
45
46
  ║ cov --port <端口号> 指定端口启动 ║
46
47
  ║ cov [路径] 在指定目录启动服务 ║
47
48
  ║ cov --help, -h 显示此帮助信息 ║
@@ -85,6 +86,7 @@ for (const arg of args) {
85
86
  const port = getPort();
86
87
  const serverArgs = [SERVER_PATH, String(port)];
87
88
  if (args.includes('--pc')) serverArgs.push('--pc');
89
+ if (args.includes('--https')) serverArgs.push('--https');
88
90
  const server = spawn(process.execPath, serverArgs, {
89
91
  stdio: 'inherit',
90
92
  cwd: process.cwd(),
package/index-pc.html CHANGED
@@ -559,6 +559,59 @@
559
559
  display: block;
560
560
  }
561
561
 
562
+ #copy-popup {
563
+ display: none;
564
+ position: fixed;
565
+ top: 50%;
566
+ left: 50%;
567
+ transform: translate(-50%, -50%);
568
+ background: #1a1a1a;
569
+ border: 1px solid #444;
570
+ border-radius: 8px;
571
+ padding: 16px;
572
+ z-index: 10000;
573
+ max-width: 80vw;
574
+ max-height: 60vh;
575
+ }
576
+ #copy-popup.show {
577
+ display: flex;
578
+ flex-direction: column;
579
+ gap: 10px;
580
+ }
581
+ #copy-popup textarea {
582
+ width: 500px;
583
+ max-width: 70vw;
584
+ height: 120px;
585
+ background: #0a0a0a;
586
+ color: #ccc;
587
+ border: 1px solid #333;
588
+ border-radius: 4px;
589
+ padding: 8px;
590
+ font-family: Menlo, Monaco, monospace;
591
+ font-size: 12px;
592
+ resize: vertical;
593
+ }
594
+ #copy-popup .popup-actions {
595
+ display: flex;
596
+ justify-content: flex-end;
597
+ gap: 8px;
598
+ }
599
+ #copy-popup button {
600
+ padding: 6px 16px;
601
+ border: none;
602
+ border-radius: 4px;
603
+ cursor: pointer;
604
+ font-size: 13px;
605
+ }
606
+ #copy-popup .btn-copy {
607
+ background: #2563eb;
608
+ color: #fff;
609
+ }
610
+ #copy-popup .btn-close {
611
+ background: #333;
612
+ color: #ccc;
613
+ }
614
+
562
615
  /* Git Diff 面板 */
563
616
  #git-diff-bar {
564
617
  display: none;
@@ -929,6 +982,14 @@
929
982
  </div>
930
983
 
931
984
  <div id="copy-toast">已复制</div>
985
+ <div id="copy-popup">
986
+ <div style="color:#ccc;font-size:13px;">剪贴板写入需要 HTTPS,请手动复制:</div>
987
+ <textarea id="copy-popup-text" readonly></textarea>
988
+ <div class="popup-actions">
989
+ <button class="btn-copy" onclick="document.getElementById('copy-popup-text').select();document.execCommand('copy');document.getElementById('copy-popup').classList.remove('show');">选中并复制</button>
990
+ <button class="btn-close" onclick="document.getElementById('copy-popup').classList.remove('show');">关闭</button>
991
+ </div>
992
+ </div>
932
993
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
933
994
  <script>
934
995
  (function() {
@@ -973,21 +1034,17 @@
973
1034
  if (idx === -1) return false;
974
1035
  var b64 = data.substring(idx + 1);
975
1036
  if (!b64 || b64 === '?') return false;
1037
+ if (isBufferReplay) return true;
976
1038
  try {
977
1039
  var text = atob(b64);
978
1040
  if (navigator.clipboard && navigator.clipboard.writeText) {
979
1041
  navigator.clipboard.writeText(text).then(function() {
980
- if (!isBufferReplay) showCopyToast();
1042
+ showCopyToast();
1043
+ }).catch(function() {
1044
+ showCopyPopup(text);
981
1045
  });
982
1046
  } else {
983
- var ta = document.createElement('textarea');
984
- ta.value = text;
985
- ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px';
986
- document.body.appendChild(ta);
987
- ta.select();
988
- document.execCommand('copy');
989
- document.body.removeChild(ta);
990
- if (!isBufferReplay) showCopyToast();
1047
+ showCopyPopup(text);
991
1048
  }
992
1049
  } catch (e) {}
993
1050
  return true;
@@ -1905,6 +1962,15 @@
1905
1962
  showCopyToast();
1906
1963
  }
1907
1964
 
1965
+ function showCopyPopup(text) {
1966
+ var popup = document.getElementById('copy-popup');
1967
+ var ta = document.getElementById('copy-popup-text');
1968
+ ta.value = text;
1969
+ popup.classList.add('show');
1970
+ ta.focus();
1971
+ ta.select();
1972
+ }
1973
+
1908
1974
  function showCopyToast() {
1909
1975
  var toast = document.getElementById('copy-toast');
1910
1976
  toast.classList.add('show');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.6.5",
3
+ "version": "2.6.6",
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
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from 'node:http';
3
- import { existsSync, createReadStream, readFileSync } from 'node:fs';
3
+ import { createServer as createHttpsServer } from 'node:https';
4
+ import { existsSync, createReadStream, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
4
5
  import { join, dirname } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { networkInterfaces, platform, arch, homedir } from 'node:os';
@@ -16,6 +17,27 @@ process.title = 'claude-opencode-viewer';
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
  const PORT = parseInt(process.argv[2]) || 7008;
18
19
  const IS_PC = process.argv.includes('--pc');
20
+ const USE_HTTPS = process.argv.includes('--https');
21
+
22
+ // 自签名证书生成
23
+ function getOrCreateCert() {
24
+ const certDir = join(homedir(), '.cov-certs');
25
+ const keyPath = join(certDir, 'key.pem');
26
+ const certPath = join(certDir, 'cert.pem');
27
+
28
+ if (existsSync(keyPath) && existsSync(certPath)) {
29
+ return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
30
+ }
31
+
32
+ console.log('🔐 首次使用 HTTPS,生成自签名证书...');
33
+ mkdirSync(certDir, { recursive: true });
34
+ execSync(
35
+ `openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 365 -nodes -subj "/CN=cov-self-signed"`,
36
+ { stdio: 'pipe' }
37
+ );
38
+ console.log(`📁 证书已保存到 ${certDir}`);
39
+ return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
40
+ }
19
41
 
20
42
  // OpenCode 数据库路径(支持环境变量覆盖,自动检测 /halo 环境)
21
43
  const OPENCODE_DB_PATH = process.env.OPENCODE_DB_PATH || join(
@@ -407,7 +429,7 @@ function getSessionMessages(sessionId) {
407
429
  }
408
430
  }
409
431
 
410
- const server = createServer(async (req, res) => {
432
+ const requestHandler = async (req, res) => {
411
433
  if (req.url === '/' || req.url === '/index.html') {
412
434
  res.writeHead(200, {
413
435
  'Content-Type': 'text/html; charset=utf-8',
@@ -557,7 +579,11 @@ const server = createServer(async (req, res) => {
557
579
 
558
580
  res.writeHead(404, { 'Content-Type': 'text/plain' });
559
581
  res.end('Not Found');
560
- });
582
+ };
583
+
584
+ const server = USE_HTTPS
585
+ ? createHttpsServer(getOrCreateCert(), requestHandler)
586
+ : createServer(requestHandler);
561
587
 
562
588
  const wss = new WebSocketServer({ server, path: '/ws' });
563
589
 
@@ -740,11 +766,13 @@ wss.on('connection', (ws, req) => {
740
766
 
741
767
  server.listen(PORT, '0.0.0.0', async () => {
742
768
  const ip = getLocalIp();
769
+ const proto = USE_HTTPS ? 'https' : 'http';
743
770
  console.log('\n' + '='.repeat(50));
744
771
  console.log('✅ Claude OpenCode Viewer 已启动');
745
772
  console.log('='.repeat(50));
746
- console.log(`🖥️ 本地访问:http://127.0.0.1:${PORT}`);
747
- console.log(`📱 手机访问:http://${ip}:${PORT}`);
773
+ console.log(`🖥️ 本地访问:${proto}://127.0.0.1:${PORT}`);
774
+ console.log(`📱 手机访问:${proto}://${ip}:${PORT}`);
775
+ if (USE_HTTPS) console.log('🔐 HTTPS 模式(首次访问需信任自签名证书)');
748
776
  console.log('='.repeat(50));
749
777
  console.log('\n按 Ctrl+C 停止服务\n');
750
778