cc-viewer 1.0.2 → 1.0.3

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 CHANGED
@@ -16,9 +16,9 @@ npm install -g cc-viewer
16
16
  ccv
17
17
  ```
18
18
 
19
- 该命令会自动将监控脚本注入到本地安装的 Claude Code 中,并在 shell 配置文件(`~/.zshrc` 或 `~/.bashrc`)中添加自动重注入 hook。之后正常使用 Claude Code,打开浏览器访问 `http://localhost:7008` 即可查看监控界面。
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 中的注入代码和 shell 配置文件中的 hook。
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
- - 系统注入标签(`<system-reminder>`、`<project-reminder>` 等)自动折叠
56
- - 自动过滤系统注入文本,只展示用户的真实输入
55
+ - 系统标签(`<system-reminder>`、`<project-reminder>` 等)自动折叠
56
+ - 自动过滤系统文本,只展示用户的真实输入
57
57
  - 支持多 session 分段展示(`/compact`、`/clear` 等操作后自动分段)
58
58
  - 每条消息显示精确到秒的时间戳
59
59
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cc-viewer",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Claude Code Logger visualization management tool",
5
5
  "license": "MIT",
6
- "main": "lib/server.js",
6
+ "main": "server.js",
7
7
  "type": "module",
8
8
  "exports": {
9
- ".": "./lib/server.js",
10
- "./server.js": "./lib/server.js",
9
+ ".": "./server.js",
10
+ "./server.js": "./server.js",
11
11
  "./interceptor.js": "./interceptor.js"
12
12
  },
13
13
  "author": "weiesky",
@@ -17,7 +17,7 @@
17
17
  "scripts": {
18
18
  "dev": "vite",
19
19
  "build": "node build.js",
20
- "start": "node lib/server.js",
20
+ "start": "node server.js",
21
21
  "prepublishOnly": "npm run build"
22
22
  },
23
23
  "keywords": [
@@ -41,7 +41,8 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "files": [
44
- "lib/",
44
+ "dist/",
45
+ "server.js",
45
46
  "cli.js",
46
47
  "interceptor.js",
47
48
  "i18n.js",
@@ -3,8 +3,8 @@ import { readFileSync, existsSync, watchFile, unwatchFile, statSync, writeFileSy
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { dirname, join, extname, basename } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
- import { LOG_FILE } from '../interceptor.js';
7
- import { t } from '../i18n.js';
6
+ import { LOG_FILE } from './interceptor.js';
7
+ import { t } from './i18n.js';
8
8
 
9
9
  const LOG_DIR = join(homedir(), '.claude', 'cc-viewer');
10
10
  const SHOW_ALL_FILE = '/tmp/cc-viewer-show-all';
@@ -56,9 +56,29 @@ 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
+
59
77
  let clients = [];
60
78
  let server;
61
79
  let actualPort = START_PORT;
80
+ // 跟踪所有被 watch 的日志文件
81
+ const watchedFiles = new Map();
62
82
 
63
83
  const MIME_TYPES = {
64
84
  '.html': 'text/html; charset=utf-8',
@@ -103,11 +123,13 @@ function sendToClients(entry) {
103
123
  });
104
124
  }
105
125
 
106
- function startWatching() {
126
+ function watchLogFile(logFile) {
127
+ if (watchedFiles.has(logFile)) return;
107
128
  let lastSize = 0;
108
- watchFile(LOG_FILE, { interval: 500 }, () => {
129
+ watchedFiles.set(logFile, true);
130
+ watchFile(logFile, { interval: 500 }, () => {
109
131
  try {
110
- const content = readFileSync(LOG_FILE, 'utf-8');
132
+ const content = readFileSync(logFile, 'utf-8');
111
133
  const newContent = content.slice(lastSize);
112
134
  lastSize = content.length;
113
135
 
@@ -128,6 +150,10 @@ function startWatching() {
128
150
  });
129
151
  }
130
152
 
153
+ function startWatching() {
154
+ watchLogFile(LOG_FILE);
155
+ }
156
+
131
157
  function handleRequest(req, res) {
132
158
  const { url, method } = req;
133
159
 
@@ -142,6 +168,29 @@ function handleRequest(req, res) {
142
168
  return;
143
169
  }
144
170
 
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
+
145
194
  // SSE endpoint
146
195
  if (url === '/events' && method === 'GET') {
147
196
  res.writeHead(200, {
@@ -308,6 +357,7 @@ export async function startViewer() {
308
357
  const alive = await checkPortAlive(existingPort);
309
358
  if (alive) {
310
359
  actualPort = existingPort;
360
+ await registerLogToServer(existingPort, LOG_FILE);
311
361
  return null;
312
362
  }
313
363
  }
@@ -326,6 +376,7 @@ export async function startViewer() {
326
376
  const alive = await checkPortAlive(existingPort);
327
377
  if (alive) {
328
378
  actualPort = existingPort;
379
+ await registerLogToServer(existingPort, LOG_FILE);
329
380
  releaseLock();
330
381
  console.log(t('server.reuse', { host: HOST, port: existingPort }));
331
382
  return null;
@@ -378,7 +429,10 @@ export async function startViewer() {
378
429
  }
379
430
 
380
431
  export function stopViewer() {
381
- unwatchFile(LOG_FILE);
432
+ for (const logFile of watchedFiles.keys()) {
433
+ unwatchFile(logFile);
434
+ }
435
+ watchedFiles.clear();
382
436
  clients.forEach(client => client.end());
383
437
  clients = [];
384
438
  if (server) {
File without changes
File without changes
File without changes