cc-viewer 1.6.253 → 1.6.255
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/dist/assets/{App-DbYqAJmv.js → App-BhRbMy4X.js} +1 -1
- package/dist/assets/{MdxEditorPanel-CbV8qPts.js → MdxEditorPanel-CfwOM0KR.js} +1 -1
- package/dist/assets/{Mobile-B0WWR1YJ.js → Mobile-COrr8adp.js} +1 -1
- package/dist/assets/ProxyModal-C-2vWlT8.js +2 -0
- package/dist/assets/{ProxyModal-I9RESzSM.css → ProxyModal-CNPuwcCz.css} +1 -1
- package/dist/assets/{index-eTBzi5lA.js → index-mBNBJDft.js} +2 -2
- package/dist/index.html +1 -1
- package/lib/terminal-env.js +102 -0
- package/package.json +25 -2
- package/pty-manager.js +8 -5
- package/scratch-pty-manager.js +4 -2
- package/server.js +19 -0
- package/dist/assets/ProxyModal-D4xpZtn8.js +0 -2
package/dist/index.html
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
if (pick) document.documentElement.setAttribute('data-theme', pick);
|
|
20
20
|
} catch {}
|
|
21
21
|
</script>
|
|
22
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-mBNBJDft.js"></script>
|
|
23
23
|
<link rel="modulepreload" crossorigin href="/assets/vendor-antd-BeN8xqGk.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-2nbmPewy.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/vendor-mdxeditor-C7DYEBoH.js">
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { getClaudeConfigDir } from '../findcc.js';
|
|
5
|
+
|
|
6
|
+
export const KEEP_CLAUDE_NO_FLICKER_ENV = 'CCV_KEEP_CLAUDE_CODE_NO_FLICKER';
|
|
7
|
+
|
|
8
|
+
function shouldKeepClaudeNoFlicker(env = {}, sourceEnv = process.env) {
|
|
9
|
+
return env[KEEP_CLAUDE_NO_FLICKER_ENV] === '1' || sourceEnv?.[KEEP_CLAUDE_NO_FLICKER_ENV] === '1';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function stripClaudeNoFlickerUnlessOptedIn(env, sourceEnv = process.env) {
|
|
13
|
+
if (!shouldKeepClaudeNoFlicker(env, sourceEnv)) {
|
|
14
|
+
delete env.CLAUDE_CODE_NO_FLICKER;
|
|
15
|
+
}
|
|
16
|
+
return env;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureWrapperFile(dir, fileName, content) {
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
const file = join(dir, fileName);
|
|
22
|
+
writeFileSync(file, content, { mode: 0o600 });
|
|
23
|
+
return file;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function defaultRcDir() {
|
|
27
|
+
return join(getClaudeConfigDir(), 'cc-viewer', 'shell-rc');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Prepare an interactive shell for cc-viewer's embedded terminals.
|
|
32
|
+
*
|
|
33
|
+
* Deleting CLAUDE_CODE_NO_FLICKER from the spawned env is enough for direct
|
|
34
|
+
* Claude spawns, but not for scratch/fallback shells: users often export the
|
|
35
|
+
* variable from ~/.zshrc or ~/.bashrc, so the shell would recreate it before
|
|
36
|
+
* they type `claude`. For those shells we install a tiny rc wrapper that loads
|
|
37
|
+
* the user's normal rc file and then unsets the variable again.
|
|
38
|
+
*/
|
|
39
|
+
export function prepareEmbeddedShellSpawn(shell, env, options = {}) {
|
|
40
|
+
const sourceEnv = options.sourceEnv || process.env;
|
|
41
|
+
const keep = shouldKeepClaudeNoFlicker(env, sourceEnv);
|
|
42
|
+
stripClaudeNoFlickerUnlessOptedIn(env, sourceEnv);
|
|
43
|
+
if (keep) return { command: shell, args: [], env };
|
|
44
|
+
|
|
45
|
+
const shellBase = basename(shell);
|
|
46
|
+
const homeDir = options.homeDir || homedir();
|
|
47
|
+
const rcDir = options.rcDir || defaultRcDir();
|
|
48
|
+
|
|
49
|
+
if (shellBase === 'zsh') {
|
|
50
|
+
const zshEnvWrapper = [
|
|
51
|
+
'# Generated by cc-viewer. Do not edit.',
|
|
52
|
+
'__ccv_wrapper_zdotdir="${ZDOTDIR:-$HOME}"',
|
|
53
|
+
'__ccv_original_zdotdir="${CCV_ORIGINAL_ZDOTDIR:-$HOME}"',
|
|
54
|
+
'if [[ "$__ccv_original_zdotdir" != "$__ccv_wrapper_zdotdir" && -r "$__ccv_original_zdotdir/.zshenv" ]]; then',
|
|
55
|
+
' source "$__ccv_original_zdotdir/.zshenv"',
|
|
56
|
+
'fi',
|
|
57
|
+
'export CCV_EFFECTIVE_ZDOTDIR="${ZDOTDIR:-$__ccv_original_zdotdir}"',
|
|
58
|
+
'export ZDOTDIR="$__ccv_wrapper_zdotdir"',
|
|
59
|
+
'unset __ccv_original_zdotdir __ccv_wrapper_zdotdir',
|
|
60
|
+
'',
|
|
61
|
+
].join('\n');
|
|
62
|
+
const zshRcWrapper = [
|
|
63
|
+
'# Generated by cc-viewer. Do not edit.',
|
|
64
|
+
'__ccv_wrapper_zdotdir="$ZDOTDIR"',
|
|
65
|
+
'__ccv_original_zdotdir="${CCV_EFFECTIVE_ZDOTDIR:-${CCV_ORIGINAL_ZDOTDIR:-$HOME}}"',
|
|
66
|
+
'if [[ "$__ccv_original_zdotdir" != "$__ccv_wrapper_zdotdir" && -r "$__ccv_original_zdotdir/.zshrc" ]]; then',
|
|
67
|
+
' ZDOTDIR="$__ccv_original_zdotdir"',
|
|
68
|
+
' source "$__ccv_original_zdotdir/.zshrc"',
|
|
69
|
+
' export ZDOTDIR="$__ccv_wrapper_zdotdir"',
|
|
70
|
+
'fi',
|
|
71
|
+
'unset CLAUDE_CODE_NO_FLICKER',
|
|
72
|
+
'unset CCV_EFFECTIVE_ZDOTDIR',
|
|
73
|
+
'unset __ccv_original_zdotdir __ccv_wrapper_zdotdir',
|
|
74
|
+
'',
|
|
75
|
+
].join('\n');
|
|
76
|
+
ensureWrapperFile(rcDir, '.zshenv', zshEnvWrapper);
|
|
77
|
+
ensureWrapperFile(rcDir, '.zshrc', zshRcWrapper);
|
|
78
|
+
env.CCV_ORIGINAL_ZDOTDIR = env.ZDOTDIR || homeDir;
|
|
79
|
+
env.ZDOTDIR = rcDir;
|
|
80
|
+
return { command: shell, args: [], env };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (shellBase === 'bash') {
|
|
84
|
+
const wrapper = [
|
|
85
|
+
'# Generated by cc-viewer. Do not edit.',
|
|
86
|
+
'if [ -r "${CCV_ORIGINAL_BASHRC:-$HOME/.bashrc}" ]; then',
|
|
87
|
+
' . "${CCV_ORIGINAL_BASHRC:-$HOME/.bashrc}"',
|
|
88
|
+
'fi',
|
|
89
|
+
'unset CLAUDE_CODE_NO_FLICKER',
|
|
90
|
+
'',
|
|
91
|
+
].join('\n');
|
|
92
|
+
const rcFile = ensureWrapperFile(rcDir, 'bashrc', wrapper);
|
|
93
|
+
env.CCV_ORIGINAL_BASHRC = env.CCV_ORIGINAL_BASHRC || join(homeDir, '.bashrc');
|
|
94
|
+
return { command: shell, args: ['--rcfile', rcFile, '-i'], env };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (shellBase === 'fish') {
|
|
98
|
+
return { command: shell, args: ['--init-command', 'set -e CLAUDE_CODE_NO_FLICKER'], env };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { command: shell, args: [], env };
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-viewer",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.255",
|
|
4
4
|
"description": "Claude Code Logger visualization management tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "server.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"build:sourcemap": "CCV_SOURCEMAP=1 node build.js",
|
|
21
21
|
"start": "node server.js",
|
|
22
22
|
"test": "CCV_LOG_DIR=tmp node --test",
|
|
23
|
-
"test:coverage": "CCV_LOG_DIR=tmp node --test --experimental-test-coverage --test-coverage-include='lib/*.js' --test-coverage-include='*.js'",
|
|
23
|
+
"test:coverage": "CCV_LOG_DIR=tmp node --test --experimental-test-coverage --test-coverage-include='lib/*.js' --test-coverage-include='src/utils/*.js' --test-coverage-include='*.js'",
|
|
24
|
+
"test:coverage:html": "CCV_LOG_DIR=tmp c8 --reporter=text-summary --reporter=html node --test",
|
|
24
25
|
"prepublishOnly": "npm run build",
|
|
25
26
|
"electron:dev": "electron electron/main.js",
|
|
26
27
|
"electron:build": "npm run build && electron-builder",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"@xterm/addon-webgl": "^0.19.0",
|
|
89
90
|
"@xterm/xterm": "^6.0.0",
|
|
90
91
|
"antd": "^5.29.2",
|
|
92
|
+
"c8": "^11.0.0",
|
|
91
93
|
"diff": "^8.0.3",
|
|
92
94
|
"electron": "^35.1.2",
|
|
93
95
|
"electron-builder": "^26.0.12",
|
|
@@ -113,5 +115,26 @@
|
|
|
113
115
|
},
|
|
114
116
|
"optionalDependencies": {
|
|
115
117
|
"@anthropic-ai/claude-agent-sdk": "^0.2.91"
|
|
118
|
+
},
|
|
119
|
+
"c8": {
|
|
120
|
+
"include": [
|
|
121
|
+
"lib/**/*.js",
|
|
122
|
+
"src/utils/**/*.js",
|
|
123
|
+
"*.js"
|
|
124
|
+
],
|
|
125
|
+
"exclude": [
|
|
126
|
+
"test/**",
|
|
127
|
+
"dist/**",
|
|
128
|
+
"node_modules/**",
|
|
129
|
+
"build.js",
|
|
130
|
+
"vite.config.js",
|
|
131
|
+
"electron/**",
|
|
132
|
+
"scripts/**",
|
|
133
|
+
"coverage/**"
|
|
134
|
+
],
|
|
135
|
+
"all": true,
|
|
136
|
+
"report-dir": "coverage",
|
|
137
|
+
"skip-full": false,
|
|
138
|
+
"check-coverage": false
|
|
116
139
|
}
|
|
117
140
|
}
|
package/pty-manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { chmodSync, statSync } from 'node:fs';
|
|
5
5
|
import { platform, arch } from 'node:os';
|
|
6
|
+
import { prepareEmbeddedShellSpawn, stripClaudeNoFlickerUnlessOptedIn } from './lib/terminal-env.js';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = dirname(__filename);
|
|
@@ -147,6 +148,9 @@ export async function spawnClaude(proxyPort, cwd, extraArgs = [], claudePath = n
|
|
|
147
148
|
env.CCV_LOG_DIR = LOG_DIR; // 让 fork 出的 Claude Code 进程找到同一份 profile.json 等资源
|
|
148
149
|
// 剥离 cc-viewer 的内部短路开关,避免泄漏给 claude 子进程
|
|
149
150
|
delete env.CCV_SKIP_THINKING_DISPLAY;
|
|
151
|
+
// Claude Code NO_FLICKER 会让嵌入式 xterm 走 alt-screen 并丢失 scrollback。
|
|
152
|
+
// cc-viewer 默认剥离继承值;确实需要时可显式设 CCV_KEEP_CLAUDE_CODE_NO_FLICKER=1。
|
|
153
|
+
stripClaudeNoFlickerUnlessOptedIn(env);
|
|
150
154
|
|
|
151
155
|
// Resolve real Node.js path (Electron's process.execPath is the Electron binary)
|
|
152
156
|
let nodePath = process.execPath;
|
|
@@ -173,8 +177,6 @@ export async function spawnClaude(proxyPort, cwd, extraArgs = [], claudePath = n
|
|
|
173
177
|
// 禁用 Claude Code CLI 的鼠标事件捕获,保住 xterm 面板原生文本选中(复制粘贴)。
|
|
174
178
|
// 不设时 Claude 会启 SGR mouse tracking (DECSET ?1000/1006),抢走 xterm 的鼠标事件。
|
|
175
179
|
// ??= 尊重用户显式 export(比如调试时想看 mouse event)。
|
|
176
|
-
// 注意:NO_FLICKER 此处**故意**不注入——它会强制 alt-screen 销毁 xterm scrollback;
|
|
177
|
-
// 需要闪烁优化的用户自行 `export CLAUDE_CODE_NO_FLICKER=1`。
|
|
178
180
|
env.CLAUDE_CODE_DISABLE_MOUSE ??= '1';
|
|
179
181
|
|
|
180
182
|
// 通过 --settings 注入 ANTHROPIC_BASE_URL,确保覆盖 settings.json 中的配置。
|
|
@@ -353,15 +355,16 @@ export async function spawnShell() {
|
|
|
353
355
|
delete shellEnv.CCVIEWER_PORT;
|
|
354
356
|
delete shellEnv.CCV_EDITOR_PORT;
|
|
355
357
|
delete shellEnv.CCVIEWER_PROTOCOL;
|
|
356
|
-
// 交互 shell 里手动敲 claude 时也禁鼠标,理由同 spawnClaude
|
|
358
|
+
// 交互 shell 里手动敲 claude 时也禁鼠标,理由同 spawnClaude。
|
|
357
359
|
shellEnv.CLAUDE_CODE_DISABLE_MOUSE ??= '1';
|
|
360
|
+
const shellSpawn = prepareEmbeddedShellSpawn(shell, shellEnv);
|
|
358
361
|
|
|
359
|
-
ptyProcess = pty.spawn(
|
|
362
|
+
ptyProcess = pty.spawn(shellSpawn.command, shellSpawn.args, {
|
|
360
363
|
name: 'xterm-256color',
|
|
361
364
|
cols: lastPtyCols,
|
|
362
365
|
rows: lastPtyRows,
|
|
363
366
|
cwd,
|
|
364
|
-
env:
|
|
367
|
+
env: shellSpawn.env,
|
|
365
368
|
});
|
|
366
369
|
|
|
367
370
|
ptyProcess.onData((data) => {
|
package/scratch-pty-manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
2
2
|
import { dirname, join, basename } from 'node:path';
|
|
3
3
|
import { chmodSync, statSync } from 'node:fs';
|
|
4
4
|
import { platform, arch, homedir } from 'node:os';
|
|
5
|
+
import { prepareEmbeddedShellSpawn } from './lib/terminal-env.js';
|
|
5
6
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
@@ -134,16 +135,17 @@ export async function spawnScratch(id) {
|
|
|
134
135
|
}
|
|
135
136
|
delete env.ANTHROPIC_BASE_URL;
|
|
136
137
|
env.CLAUDE_CODE_DISABLE_MOUSE ??= '1';
|
|
138
|
+
const shellSpawn = prepareEmbeddedShellSpawn(shell, env);
|
|
137
139
|
|
|
138
140
|
s.lastExitCode = null;
|
|
139
141
|
s.outputBuffer = '';
|
|
140
142
|
|
|
141
|
-
s.ptyProcess = pty.spawn(
|
|
143
|
+
s.ptyProcess = pty.spawn(shellSpawn.command, shellSpawn.args, {
|
|
142
144
|
name: 'xterm-256color',
|
|
143
145
|
cols: s.lastCols,
|
|
144
146
|
rows: s.lastRows,
|
|
145
147
|
cwd: STARTUP_CWD,
|
|
146
|
-
env,
|
|
148
|
+
env: shellSpawn.env,
|
|
147
149
|
});
|
|
148
150
|
|
|
149
151
|
s.ptyProcess.onData((data) => {
|
package/server.js
CHANGED
|
@@ -59,6 +59,8 @@ function getPrefsFile() { return join(LOG_DIR, 'preferences.json'); }
|
|
|
59
59
|
|
|
60
60
|
// 启动时一次性读取 ~/.claude/settings.json(不 watch)
|
|
61
61
|
let claudeSettings = {};
|
|
62
|
+
// SSR theme 注入自检状态:模板缺 data-theme 时仅首次 warn(避免高 QPS 刷屏)
|
|
63
|
+
let _ssrThemeAttrWarned = false;
|
|
62
64
|
try {
|
|
63
65
|
const settingsPath = join(getClaudeConfigDir(), 'settings.json');
|
|
64
66
|
if (existsSync(settingsPath)) {
|
|
@@ -2382,6 +2384,10 @@ async function handleRequest(req, res) {
|
|
|
2382
2384
|
return;
|
|
2383
2385
|
}
|
|
2384
2386
|
} else {
|
|
2387
|
+
// Fallback id:当 hook caller 没传 toolUseId(如老 Claude Code PreToolUse hook
|
|
2388
|
+
// payload 不含 tool_use_id),生成 ask_${ts}_${rnd} 占位。这个 id 与 jsonl 里
|
|
2389
|
+
// tool_use.id(toolu_xxx)不同名,前端 portal 决策必须按 ask_* 前缀通配命中。
|
|
2390
|
+
// 协议锚点:src/utils/askPortalMatcher.js — 改此处前缀格式必须同步改 matcher。
|
|
2385
2391
|
do { id = `ask_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; } while (pendingAskHooks.has(id));
|
|
2386
2392
|
}
|
|
2387
2393
|
|
|
@@ -3618,6 +3624,13 @@ async function handleRequest(req, res) {
|
|
|
3618
3624
|
// 等 React 拿到 prefs 才切回 dark,肉眼可见一次"白闪"。
|
|
3619
3625
|
// 这里把当前 prefs 里的 themeColor 直接写进 HTML,inline boot script 仍负责
|
|
3620
3626
|
// 处理 URL ?theme= 优先级与 localStorage 缓存。
|
|
3627
|
+
//
|
|
3628
|
+
// 主题来源优先级(首屏 → React 接管后):
|
|
3629
|
+
// 1. URL ?theme= (inline boot script 读取,最高优先)
|
|
3630
|
+
// 2. localStorage ccv_themeColor (inline boot script 读取,跨刷新缓存)
|
|
3631
|
+
// 3. preferences.json 的 themeColor (此处 SSR 注入到 <html data-theme="...">,老用户兜底)
|
|
3632
|
+
// 4. dist/index.html 模板里的硬编码 default ("light")
|
|
3633
|
+
// React 接管后 AppBase._applyTheme() 会基于 1/2/3 重新统一 state + DOM + localStorage 三向同步。
|
|
3621
3634
|
const serveIndexHtml = () => {
|
|
3622
3635
|
try {
|
|
3623
3636
|
const indexPath = join(__dirname, 'dist', 'index.html');
|
|
@@ -3629,6 +3642,12 @@ async function handleRequest(req, res) {
|
|
|
3629
3642
|
if (prefs.themeColor === 'dark' || prefs.themeColor === 'light') themeColor = prefs.themeColor;
|
|
3630
3643
|
}
|
|
3631
3644
|
} catch { /* 读 prefs 失败就走默认 light */ }
|
|
3645
|
+
// 自检:模板里没有 <html ... data-theme="..."> 时 replace 静默 no-op,SSR 优化失效但不报错。
|
|
3646
|
+
// 仅首次 warn 避免高 QPS 刷屏(_ssrThemeAttrWarned 单进程一次性)。
|
|
3647
|
+
if (!_ssrThemeAttrWarned && !/<html[^>]*data-theme="[^"]*"/.test(html)) {
|
|
3648
|
+
_ssrThemeAttrWarned = true;
|
|
3649
|
+
console.warn('[serveIndexHtml] dist/index.html 没有 <html data-theme="..."> 属性,SSR theme 注入将不生效。检查 index.html 模板。');
|
|
3650
|
+
}
|
|
3632
3651
|
html = html.replace(/<html([^>]*?)data-theme="[^"]*"/, `<html$1data-theme="${themeColor}"`);
|
|
3633
3652
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
|
|
3634
3653
|
res.end(html);
|