cc-viewer 0.1.1 → 0.1.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/interceptor.js +3 -0
- package/lib/assets/index-B9Z7D9kT.css +1 -0
- package/lib/assets/index-Cz06a4Sw.js +395 -0
- package/lib/index.html +5 -48
- package/lib/server.js +58 -67
- package/package.json +14 -3
- package/lib/app.js +0 -753
- package/lib/style.css +0 -824
- package/lib/vendor/json-viewer.bundle.js +0 -1289
- package/lib/vendor/marked.umd.js +0 -74
package/lib/index.html
CHANGED
|
@@ -4,54 +4,11 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Claude Code Viewer</title>
|
|
7
|
-
<
|
|
7
|
+
<style>body {margin: 0}</style>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Cz06a4Sw.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B9Z7D9kT.css">
|
|
8
10
|
</head>
|
|
9
11
|
<body>
|
|
10
|
-
<div
|
|
11
|
-
<div class="header-left">
|
|
12
|
-
<h1>Claude Code Viewer</h1>
|
|
13
|
-
<div class="status-badge">
|
|
14
|
-
<div class="status-dot"></div>
|
|
15
|
-
<span>实时监控中</span>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="header-right">
|
|
19
|
-
<div class="stat-badge">
|
|
20
|
-
总请求数: <strong id="total-count">0</strong>
|
|
21
|
-
</div>
|
|
22
|
-
<button id="mode-toggle-btn" onclick="toggleViewMode()">打开对话模式</button>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div class="main-container">
|
|
27
|
-
<div class="left-panel" id="left-panel">
|
|
28
|
-
<div class="panel-header">请求列表</div>
|
|
29
|
-
<div class="request-list" id="request-list">
|
|
30
|
-
<div class="empty-state">
|
|
31
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
32
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
|
|
33
|
-
</svg>
|
|
34
|
-
<p>等待请求...</p>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<div class="resizer" id="resizer"></div>
|
|
40
|
-
|
|
41
|
-
<div class="right-panel" id="right-panel">
|
|
42
|
-
<div class="empty-detail">
|
|
43
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"></path>
|
|
45
|
-
</svg>
|
|
46
|
-
<p>选择一个请求查看详情</p>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<div id="chat-container" class="chat-container" style="display: none;"></div>
|
|
52
|
-
|
|
53
|
-
<script src="/vendor/marked.umd.js"></script>
|
|
54
|
-
<script src="/vendor/json-viewer.bundle.js"></script>
|
|
55
|
-
<script src="/app.js" defer></script>
|
|
12
|
+
<div id="root"></div>
|
|
56
13
|
</body>
|
|
57
|
-
</html>
|
|
14
|
+
</html>
|
package/lib/server.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { readFileSync, existsSync, watchFile, unwatchFile } from 'node:fs';
|
|
2
|
+
import { readFileSync, existsSync, watchFile, unwatchFile, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { dirname, join } from 'node:path';
|
|
5
|
-
import {
|
|
4
|
+
import { dirname, join, extname } from 'node:path';
|
|
5
|
+
import { LOG_FILE } from '../interceptor.js';
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
9
|
const START_PORT = 7008;
|
|
10
10
|
const MAX_PORT = 7099;
|
|
11
11
|
const HOST = '127.0.0.1';
|
|
12
|
+
const PORT_FILE = '/tmp/cc-viewer-port';
|
|
12
13
|
|
|
13
14
|
let clients = [];
|
|
14
15
|
let server;
|
|
15
16
|
let actualPort = START_PORT;
|
|
16
17
|
|
|
18
|
+
const MIME_TYPES = {
|
|
19
|
+
'.html': 'text/html; charset=utf-8',
|
|
20
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
21
|
+
'.css': 'text/css; charset=utf-8',
|
|
22
|
+
'.json': 'application/json; charset=utf-8',
|
|
23
|
+
'.svg': 'image/svg+xml',
|
|
24
|
+
'.png': 'image/png',
|
|
25
|
+
'.jpg': 'image/jpeg',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
};
|
|
29
|
+
|
|
17
30
|
function readLogFile() {
|
|
18
31
|
if (!existsSync(LOG_FILE)) {
|
|
19
32
|
return [];
|
|
@@ -84,64 +97,8 @@ function handleRequest(req, res) {
|
|
|
84
97
|
return;
|
|
85
98
|
}
|
|
86
99
|
|
|
87
|
-
// Serve HTML
|
|
88
|
-
if (url === '/' && method === 'GET') {
|
|
89
|
-
try {
|
|
90
|
-
const htmlPath = join(__dirname, 'index.html');
|
|
91
|
-
const html = readFileSync(htmlPath, 'utf-8');
|
|
92
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
93
|
-
res.end(html);
|
|
94
|
-
} catch (err) {
|
|
95
|
-
res.writeHead(500);
|
|
96
|
-
res.end('Error loading page');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// Serve CSS
|
|
100
|
-
else if (url === '/style.css' && method === 'GET') {
|
|
101
|
-
try {
|
|
102
|
-
const cssPath = join(__dirname, 'style.css');
|
|
103
|
-
const css = readFileSync(cssPath, 'utf-8');
|
|
104
|
-
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8' });
|
|
105
|
-
res.end(css);
|
|
106
|
-
} catch (err) {
|
|
107
|
-
res.writeHead(500);
|
|
108
|
-
res.end('Error loading CSS');
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// Serve JavaScript
|
|
112
|
-
else if (url === '/app.js' && method === 'GET') {
|
|
113
|
-
try {
|
|
114
|
-
const jsPath = join(__dirname, 'app.js');
|
|
115
|
-
const js = readFileSync(jsPath, 'utf-8');
|
|
116
|
-
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
|
|
117
|
-
res.end(js);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
res.writeHead(500);
|
|
120
|
-
res.end('Error loading JavaScript');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Serve node_modules files
|
|
124
|
-
else if (url.startsWith('/vendor/') && method === 'GET') {
|
|
125
|
-
try {
|
|
126
|
-
const filePath = join(__dirname, url);
|
|
127
|
-
const content = readFileSync(filePath);
|
|
128
|
-
|
|
129
|
-
let contentType = 'application/octet-stream';
|
|
130
|
-
if (url.endsWith('.js')) {
|
|
131
|
-
contentType = 'application/javascript; charset=utf-8';
|
|
132
|
-
} else if (url.endsWith('.css')) {
|
|
133
|
-
contentType = 'text/css; charset=utf-8';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
137
|
-
res.end(content);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
res.writeHead(404);
|
|
140
|
-
res.end('File not found');
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
100
|
// SSE endpoint
|
|
144
|
-
|
|
101
|
+
if (url === '/events' && method === 'GET') {
|
|
145
102
|
res.writeHead(200, {
|
|
146
103
|
'Content-Type': 'text/event-stream',
|
|
147
104
|
'Cache-Control': 'no-cache',
|
|
@@ -158,23 +115,56 @@ function handleRequest(req, res) {
|
|
|
158
115
|
req.on('close', () => {
|
|
159
116
|
clients = clients.filter(client => client !== res);
|
|
160
117
|
});
|
|
118
|
+
return;
|
|
161
119
|
}
|
|
120
|
+
|
|
162
121
|
// API endpoint
|
|
163
|
-
|
|
122
|
+
if (url === '/api/requests' && method === 'GET') {
|
|
164
123
|
const entries = readLogFile();
|
|
165
124
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
166
125
|
res.end(JSON.stringify(entries));
|
|
126
|
+
return;
|
|
167
127
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
128
|
+
|
|
129
|
+
// 静态文件服务
|
|
130
|
+
if (method === 'GET') {
|
|
131
|
+
let filePath = url === '/' ? '/index.html' : url;
|
|
132
|
+
// 去掉 query string
|
|
133
|
+
filePath = filePath.split('?')[0];
|
|
134
|
+
|
|
135
|
+
const fullPath = join(__dirname, filePath);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
if (existsSync(fullPath) && statSync(fullPath).isFile()) {
|
|
139
|
+
const content = readFileSync(fullPath);
|
|
140
|
+
const ext = extname(filePath);
|
|
141
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
142
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
143
|
+
res.end(content);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// fall through to SPA fallback
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// SPA fallback: 非 API/非静态文件请求返回 index.html
|
|
151
|
+
try {
|
|
152
|
+
const indexPath = join(__dirname, 'index.html');
|
|
153
|
+
const html = readFileSync(indexPath, 'utf-8');
|
|
154
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
155
|
+
res.end(html);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
res.writeHead(404);
|
|
158
|
+
res.end('Not Found');
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
171
161
|
}
|
|
162
|
+
|
|
163
|
+
res.writeHead(404);
|
|
164
|
+
res.end('Not Found');
|
|
172
165
|
}
|
|
173
166
|
|
|
174
167
|
export function startViewer() {
|
|
175
|
-
// 首先设置拦截器
|
|
176
|
-
setupInterceptor();
|
|
177
|
-
|
|
178
168
|
return new Promise((resolve, reject) => {
|
|
179
169
|
function tryListen(port) {
|
|
180
170
|
if (port > MAX_PORT) {
|
|
@@ -188,6 +178,7 @@ export function startViewer() {
|
|
|
188
178
|
currentServer.listen(port, HOST, () => {
|
|
189
179
|
server = currentServer;
|
|
190
180
|
actualPort = port;
|
|
181
|
+
try { writeFileSync(PORT_FILE, String(port)); } catch {}
|
|
191
182
|
console.log(`\n🔍 Claude 请求监控服务已启动: http://${HOST}:${port}\n`);
|
|
192
183
|
startWatching();
|
|
193
184
|
resolve(server);
|
package/package.json
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-viewer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Claude Code Logger visualization management tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "lib/server.js",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/server.js",
|
|
10
|
+
"./server.js": "./lib/server.js",
|
|
11
|
+
"./interceptor.js": "./interceptor.js"
|
|
12
|
+
},
|
|
8
13
|
"author": "weiesky",
|
|
9
14
|
"bin": {
|
|
10
15
|
"ccviewer": "cli.js"
|
|
11
16
|
},
|
|
12
17
|
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
13
19
|
"build": "node build.js",
|
|
14
20
|
"start": "node lib/server.js",
|
|
15
21
|
"prepublishOnly": "npm run build"
|
|
@@ -40,7 +46,12 @@
|
|
|
40
46
|
"interceptor.js"
|
|
41
47
|
],
|
|
42
48
|
"devDependencies": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
49
|
+
"react": "^18.3.1",
|
|
50
|
+
"react-dom": "^18.3.1",
|
|
51
|
+
"antd": "^5.29.2",
|
|
52
|
+
"marked": "^17.0.2",
|
|
53
|
+
"react-json-view-lite": "^2.1.0",
|
|
54
|
+
"vite": "^6.3.5",
|
|
55
|
+
"@vitejs/plugin-react": "^4.5.2"
|
|
45
56
|
}
|
|
46
57
|
}
|