claude-opencode-viewer 2.6.56 → 2.6.58
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/index-pc.html +45 -6
- package/package.json +1 -1
- package/server.js +8 -32
package/index-pc.html
CHANGED
|
@@ -731,6 +731,31 @@
|
|
|
731
731
|
overflow: auto;
|
|
732
732
|
padding: 16px;
|
|
733
733
|
}
|
|
734
|
+
.docs-toolbar {
|
|
735
|
+
display: flex;
|
|
736
|
+
justify-content: flex-end;
|
|
737
|
+
margin-bottom: 8px;
|
|
738
|
+
border-bottom: 1px solid #333;
|
|
739
|
+
padding-bottom: 8px;
|
|
740
|
+
}
|
|
741
|
+
.docs-copy-btn {
|
|
742
|
+
display: inline-flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
gap: 4px;
|
|
745
|
+
padding: 4px 10px;
|
|
746
|
+
background: transparent;
|
|
747
|
+
border: 1px solid #555;
|
|
748
|
+
color: #aaa;
|
|
749
|
+
font-size: 12px;
|
|
750
|
+
border-radius: 4px;
|
|
751
|
+
cursor: pointer;
|
|
752
|
+
transition: all 0.2s;
|
|
753
|
+
}
|
|
754
|
+
.docs-copy-btn:hover {
|
|
755
|
+
background: #333;
|
|
756
|
+
border-color: #777;
|
|
757
|
+
color: #fff;
|
|
758
|
+
}
|
|
734
759
|
.docs-content-area pre {
|
|
735
760
|
margin: 0;
|
|
736
761
|
white-space: pre-wrap;
|
|
@@ -959,7 +984,6 @@
|
|
|
959
984
|
<option value="" disabled selected hidden>--</option>
|
|
960
985
|
<option value="opencode">OpenCode</option>
|
|
961
986
|
<option value="claude">Claude</option>
|
|
962
|
-
<option value="qoder">Qoder</option>
|
|
963
987
|
<option value="shell">Shell</option>
|
|
964
988
|
</select>
|
|
965
989
|
</div>
|
|
@@ -1121,7 +1145,7 @@
|
|
|
1121
1145
|
var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
1122
1146
|
var isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
1123
1147
|
var MOBILE_COLS = 60;
|
|
1124
|
-
var fontSize = isMobile ? 11 :
|
|
1148
|
+
var fontSize = isMobile ? 11 : 14;
|
|
1125
1149
|
var currentMode = 'claude';
|
|
1126
1150
|
var isTransitioning = false;
|
|
1127
1151
|
var transitionEndTimer = null;
|
|
@@ -1134,7 +1158,7 @@
|
|
|
1134
1158
|
cursorBlink: !isMobile,
|
|
1135
1159
|
fontSize: fontSize,
|
|
1136
1160
|
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
1137
|
-
lineHeight: 1.
|
|
1161
|
+
lineHeight: 1.25,
|
|
1138
1162
|
letterSpacing: 0,
|
|
1139
1163
|
theme: {
|
|
1140
1164
|
background: '#0a0a0a',
|
|
@@ -1680,7 +1704,7 @@
|
|
|
1680
1704
|
currentMode = mode;
|
|
1681
1705
|
modeSelect.value = mode;
|
|
1682
1706
|
document.getElementById('mode-label').textContent = '';
|
|
1683
|
-
var label = mode === 'claude' ? 'Claude' : mode === 'shell' ? 'Shell' :
|
|
1707
|
+
var label = mode === 'claude' ? 'Claude' : mode === 'shell' ? 'Shell' : 'OpenCode';
|
|
1684
1708
|
var initOv = document.getElementById('init-overlay');
|
|
1685
1709
|
initOv.textContent = '正在启动 ' + label + (sessionId ? '(恢复会话)' : '');
|
|
1686
1710
|
initOv.classList.add('visible');
|
|
@@ -1786,7 +1810,7 @@
|
|
|
1786
1810
|
document.getElementById('mode-label').textContent = '';
|
|
1787
1811
|
}
|
|
1788
1812
|
if (msg.running) {
|
|
1789
|
-
var mLabel = msg.mode === 'claude' ? 'Claude' : msg.mode === '
|
|
1813
|
+
var mLabel = msg.mode === 'claude' ? 'Claude' : msg.mode === 'shell' ? 'Shell' : 'OpenCode';
|
|
1790
1814
|
var initOv = document.getElementById('init-overlay');
|
|
1791
1815
|
initOv.textContent = '正在启动 ' + mLabel + '...';
|
|
1792
1816
|
initOv.classList.add('visible');
|
|
@@ -2679,7 +2703,22 @@
|
|
|
2679
2703
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">' + escapeHtml(data.error) + '</div>';
|
|
2680
2704
|
return;
|
|
2681
2705
|
}
|
|
2682
|
-
|
|
2706
|
+
var rawContent = data.content || '';
|
|
2707
|
+
var html = '<div class="docs-toolbar">' +
|
|
2708
|
+
'<button class="docs-copy-btn" title="复制">' +
|
|
2709
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
2710
|
+
'<rect x="9" y="9" width="13" height="13" rx="2"></rect>' +
|
|
2711
|
+
'<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>' +
|
|
2712
|
+
'</svg></button></div>' +
|
|
2713
|
+
'<div class="docs-md-content">' + formatDocContent(rawContent) + '</div>';
|
|
2714
|
+
area.innerHTML = html;
|
|
2715
|
+
area.querySelector('.docs-copy-btn').addEventListener('click', function() {
|
|
2716
|
+
var btn = area.querySelector('.docs-copy-btn');
|
|
2717
|
+
navigator.clipboard.writeText(rawContent).then(function() {
|
|
2718
|
+
btn.title = '已复制';
|
|
2719
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#58d68d" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
2720
|
+
});
|
|
2721
|
+
});
|
|
2683
2722
|
})
|
|
2684
2723
|
.catch(function(err) {
|
|
2685
2724
|
area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -91,7 +91,6 @@ let ptyModule = null;
|
|
|
91
91
|
let claudeProcess = null;
|
|
92
92
|
let opencodeProcess = null;
|
|
93
93
|
let shellProcess = null;
|
|
94
|
-
let qoderProcess = null;
|
|
95
94
|
let currentProcess = null;
|
|
96
95
|
let outputBuffer = '';
|
|
97
96
|
const dataListeners = [];
|
|
@@ -247,12 +246,8 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
247
246
|
} else if (mode === 'shell') {
|
|
248
247
|
// 普通 shell 终端: 优先使用 $SHELL,回退到 zsh/bash
|
|
249
248
|
command = process.env.SHELL || (existsSync('/bin/zsh') ? '/bin/zsh' : '/bin/bash');
|
|
250
|
-
args = ['-l']; // 登录
|
|
249
|
+
args = ['-l', '-i']; // 登录 + 交互模式,加载 PATH/alias 并确保 complete 等命令可用
|
|
251
250
|
LOG(`[shell] 启动 shell: ${command} ${args.join(' ')}`);
|
|
252
|
-
} else if (mode === 'qoder') {
|
|
253
|
-
command = findCommand('qodercli');
|
|
254
|
-
args = [];
|
|
255
|
-
LOG(`[qoder] 启动 qodercli: ${command}`);
|
|
256
251
|
} else {
|
|
257
252
|
const t2 = Date.now();
|
|
258
253
|
command = findCommand('opencode');
|
|
@@ -350,9 +345,6 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
350
345
|
if (shellProcess === proc) {
|
|
351
346
|
shellProcess = null;
|
|
352
347
|
}
|
|
353
|
-
if (qoderProcess === proc) {
|
|
354
|
-
qoderProcess = null;
|
|
355
|
-
}
|
|
356
348
|
// 已被替换的旧进程或切换中的进程,不通知前端
|
|
357
349
|
if (isSwitching || currentProcess !== null) return;
|
|
358
350
|
exitListeners.forEach(cb => cb(exitCode || 0));
|
|
@@ -375,14 +367,6 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
375
367
|
} catch {}
|
|
376
368
|
}
|
|
377
369
|
shellProcess = proc;
|
|
378
|
-
} else if (mode === 'qoder') {
|
|
379
|
-
if (qoderProcess && qoderProcess !== proc && qoderProcess.pid) {
|
|
380
|
-
try {
|
|
381
|
-
LOG(`[spawnProcess] 清理旧 qoder 进程 PID: ${qoderProcess.pid}`);
|
|
382
|
-
killProcessTree(qoderProcess);
|
|
383
|
-
} catch {}
|
|
384
|
-
}
|
|
385
|
-
qoderProcess = proc;
|
|
386
370
|
} else {
|
|
387
371
|
if (opencodeProcess && opencodeProcess !== proc && opencodeProcess.pid) {
|
|
388
372
|
try {
|
|
@@ -403,7 +387,7 @@ async function spawnProcess(mode, sessionId = null) {
|
|
|
403
387
|
}
|
|
404
388
|
|
|
405
389
|
currentProcess = proc;
|
|
406
|
-
const modeLabel = mode === 'claude' ? 'Claude Code' : mode === 'shell' ? 'Shell' :
|
|
390
|
+
const modeLabel = mode === 'claude' ? 'Claude Code' : mode === 'shell' ? 'Shell' : 'OpenCode';
|
|
407
391
|
LOG(`[claude-opencode-viewer] ${modeLabel} 已启动 (PID: ${proc.pid})`);
|
|
408
392
|
return proc;
|
|
409
393
|
}
|
|
@@ -443,14 +427,6 @@ async function switchMode(newMode) {
|
|
|
443
427
|
LOG('[switchMode] 杀死 shell 进程失败:', e.message);
|
|
444
428
|
}
|
|
445
429
|
shellProcess = null;
|
|
446
|
-
} else if (currentMode === 'qoder' && qoderProcess) {
|
|
447
|
-
try {
|
|
448
|
-
LOG(`[switchMode] 杀死 qoder 进程 PID: ${qoderProcess.pid}`);
|
|
449
|
-
killProcessTree(qoderProcess);
|
|
450
|
-
} catch (e) {
|
|
451
|
-
LOG('[switchMode] 杀死 qoder 进程失败:', e.message);
|
|
452
|
-
}
|
|
453
|
-
qoderProcess = null;
|
|
454
430
|
}
|
|
455
431
|
currentProcess = null;
|
|
456
432
|
|
|
@@ -489,7 +465,7 @@ function resizePty(cols, rows) {
|
|
|
489
465
|
lastPtyCols = cols;
|
|
490
466
|
lastPtyRows = rows;
|
|
491
467
|
if (sameSize) return;
|
|
492
|
-
[claudeProcess, opencodeProcess].forEach(proc => {
|
|
468
|
+
[claudeProcess, opencodeProcess, shellProcess].forEach(proc => {
|
|
493
469
|
if (proc) {
|
|
494
470
|
try {
|
|
495
471
|
// 先设不同尺寸再设目标尺寸,保证即使进程已是该尺寸也能触发 SIGWINCH
|
|
@@ -1547,13 +1523,13 @@ function startServer() {
|
|
|
1547
1523
|
cleanupOrphanProcesses();
|
|
1548
1524
|
|
|
1549
1525
|
if (INIT_SHELL) {
|
|
1550
|
-
// PC端默认进入
|
|
1551
|
-
currentMode = '
|
|
1526
|
+
// PC端默认进入 Shell 模式
|
|
1527
|
+
currentMode = 'shell';
|
|
1552
1528
|
try {
|
|
1553
|
-
await spawnProcess('
|
|
1554
|
-
LOG('[startup]
|
|
1529
|
+
await spawnProcess('shell');
|
|
1530
|
+
LOG('[startup] Shell 已启动');
|
|
1555
1531
|
} catch (e) {
|
|
1556
|
-
LOG('[startup]
|
|
1532
|
+
LOG('[startup] Shell 启动失败:', e.message);
|
|
1557
1533
|
}
|
|
1558
1534
|
} else {
|
|
1559
1535
|
// 延迟启动:等待 PC 客户端发送 init 消息后再 spawn 进程
|