cc-viewer 1.0.1 → 1.0.2
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/README.md +5 -5
- package/interceptor.js +4 -2
- package/lib/server.js +4 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ npm install -g cc-viewer
|
|
|
16
16
|
ccv
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
该命令会自动将监控脚本注入到本地安装的 Claude Code 中,并在 shell 配置文件(`~/.zshrc` 或 `~/.bashrc`)中添加自动重注入 hook。之后正常使用 Claude Code,打开浏览器访问 `http://localhost:7008` 即可查看监控界面。
|
|
20
20
|
|
|
21
|
-
Claude Code 更新后无需手动操作,下次运行 `claude`
|
|
21
|
+
Claude Code 更新后无需手动操作,下次运行 `claude` 时会自动检测并重新注入。
|
|
22
22
|
|
|
23
23
|
### 卸载
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ Claude Code 更新后无需手动操作,下次运行 `claude` 时会自动检
|
|
|
26
26
|
ccv --uninstall
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
一键清理 cli.js
|
|
29
|
+
一键清理 cli.js 中的注入代码和 shell 配置文件中的 hook。
|
|
30
30
|
|
|
31
31
|
## 功能
|
|
32
32
|
|
|
@@ -52,8 +52,8 @@ ccv --uninstall
|
|
|
52
52
|
- `thinking` 块默认折叠,点击展开查看思考过程
|
|
53
53
|
- `tool_use` 显示为紧凑的工具调用卡片(Bash、Read、Edit、Write、Glob、Grep、Task 等均有专属展示)
|
|
54
54
|
- 用户选择型消息(AskUserQuestion)以问答形式展示
|
|
55
|
-
-
|
|
56
|
-
-
|
|
55
|
+
- 系统注入标签(`<system-reminder>`、`<project-reminder>` 等)自动折叠
|
|
56
|
+
- 自动过滤系统注入文本,只展示用户的真实输入
|
|
57
57
|
- 支持多 session 分段展示(`/compact`、`/clear` 等操作后自动分段)
|
|
58
58
|
- 每条消息显示精确到秒的时间戳
|
|
59
59
|
|
package/interceptor.js
CHANGED
|
@@ -18,7 +18,9 @@ function generateLogFilePath() {
|
|
|
18
18
|
+ String(now.getHours()).padStart(2, '0')
|
|
19
19
|
+ String(now.getMinutes()).padStart(2, '0')
|
|
20
20
|
+ String(now.getSeconds()).padStart(2, '0');
|
|
21
|
-
|
|
21
|
+
let cwd;
|
|
22
|
+
try { cwd = process.cwd(); } catch { cwd = homedir(); }
|
|
23
|
+
const projectName = basename(cwd).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
|
|
22
24
|
const dir = join(homedir(), '.claude', 'cc-viewer');
|
|
23
25
|
try { mkdirSync(dir, { recursive: true }); } catch {}
|
|
24
26
|
return join(dir, `${projectName}_${ts}.jsonl`);
|
|
@@ -208,7 +210,7 @@ export function setupInterceptor() {
|
|
|
208
210
|
|
|
209
211
|
requestEntry = {
|
|
210
212
|
timestamp,
|
|
211
|
-
project: basename(process.cwd()),
|
|
213
|
+
project: (() => { try { return basename(process.cwd()); } catch { return 'unknown'; } })(),
|
|
212
214
|
url: urlStr,
|
|
213
215
|
method: options?.method || 'GET',
|
|
214
216
|
headers,
|
package/lib/server.js
CHANGED
|
@@ -56,29 +56,9 @@ function checkPortAlive(port) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function registerLogToServer(port, logFile) {
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
const data = JSON.stringify({ logFile });
|
|
62
|
-
const req = httpRequest({
|
|
63
|
-
host: HOST, port, path: '/api/register-log', method: 'POST',
|
|
64
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
|
|
65
|
-
timeout: 2000,
|
|
66
|
-
}, (res) => {
|
|
67
|
-
res.resume();
|
|
68
|
-
resolve(true);
|
|
69
|
-
});
|
|
70
|
-
req.on('error', () => resolve(false));
|
|
71
|
-
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
72
|
-
req.write(data);
|
|
73
|
-
req.end();
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
59
|
let clients = [];
|
|
78
60
|
let server;
|
|
79
61
|
let actualPort = START_PORT;
|
|
80
|
-
// 跟踪所有被 watch 的日志文件
|
|
81
|
-
const watchedFiles = new Map();
|
|
82
62
|
|
|
83
63
|
const MIME_TYPES = {
|
|
84
64
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -123,13 +103,11 @@ function sendToClients(entry) {
|
|
|
123
103
|
});
|
|
124
104
|
}
|
|
125
105
|
|
|
126
|
-
function
|
|
127
|
-
if (watchedFiles.has(logFile)) return;
|
|
106
|
+
function startWatching() {
|
|
128
107
|
let lastSize = 0;
|
|
129
|
-
|
|
130
|
-
watchFile(logFile, { interval: 500 }, () => {
|
|
108
|
+
watchFile(LOG_FILE, { interval: 500 }, () => {
|
|
131
109
|
try {
|
|
132
|
-
const content = readFileSync(
|
|
110
|
+
const content = readFileSync(LOG_FILE, 'utf-8');
|
|
133
111
|
const newContent = content.slice(lastSize);
|
|
134
112
|
lastSize = content.length;
|
|
135
113
|
|
|
@@ -150,10 +128,6 @@ function watchLogFile(logFile) {
|
|
|
150
128
|
});
|
|
151
129
|
}
|
|
152
130
|
|
|
153
|
-
function startWatching() {
|
|
154
|
-
watchLogFile(LOG_FILE);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
131
|
function handleRequest(req, res) {
|
|
158
132
|
const { url, method } = req;
|
|
159
133
|
|
|
@@ -168,29 +142,6 @@ function handleRequest(req, res) {
|
|
|
168
142
|
return;
|
|
169
143
|
}
|
|
170
144
|
|
|
171
|
-
// 注册新的日志文件进行 watch(供新进程复用旧服务时调用)
|
|
172
|
-
if (url === '/api/register-log' && method === 'POST') {
|
|
173
|
-
let body = '';
|
|
174
|
-
req.on('data', chunk => { body += chunk; });
|
|
175
|
-
req.on('end', () => {
|
|
176
|
-
try {
|
|
177
|
-
const { logFile } = JSON.parse(body);
|
|
178
|
-
if (logFile && typeof logFile === 'string' && logFile.startsWith(LOG_DIR) && existsSync(logFile)) {
|
|
179
|
-
watchLogFile(logFile);
|
|
180
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
181
|
-
res.end(JSON.stringify({ ok: true }));
|
|
182
|
-
} else {
|
|
183
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
184
|
-
res.end(JSON.stringify({ error: 'Invalid log file path' }));
|
|
185
|
-
}
|
|
186
|
-
} catch {
|
|
187
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
188
|
-
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
145
|
// SSE endpoint
|
|
195
146
|
if (url === '/events' && method === 'GET') {
|
|
196
147
|
res.writeHead(200, {
|
|
@@ -357,7 +308,6 @@ export async function startViewer() {
|
|
|
357
308
|
const alive = await checkPortAlive(existingPort);
|
|
358
309
|
if (alive) {
|
|
359
310
|
actualPort = existingPort;
|
|
360
|
-
await registerLogToServer(existingPort, LOG_FILE);
|
|
361
311
|
return null;
|
|
362
312
|
}
|
|
363
313
|
}
|
|
@@ -376,7 +326,6 @@ export async function startViewer() {
|
|
|
376
326
|
const alive = await checkPortAlive(existingPort);
|
|
377
327
|
if (alive) {
|
|
378
328
|
actualPort = existingPort;
|
|
379
|
-
await registerLogToServer(existingPort, LOG_FILE);
|
|
380
329
|
releaseLock();
|
|
381
330
|
console.log(t('server.reuse', { host: HOST, port: existingPort }));
|
|
382
331
|
return null;
|
|
@@ -429,10 +378,7 @@ export async function startViewer() {
|
|
|
429
378
|
}
|
|
430
379
|
|
|
431
380
|
export function stopViewer() {
|
|
432
|
-
|
|
433
|
-
unwatchFile(logFile);
|
|
434
|
-
}
|
|
435
|
-
watchedFiles.clear();
|
|
381
|
+
unwatchFile(LOG_FILE);
|
|
436
382
|
clients.forEach(client => client.end());
|
|
437
383
|
clients = [];
|
|
438
384
|
if (server) {
|