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/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
- <link rel="stylesheet" href="style.css">
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 class="header">
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 { setupInterceptor, LOG_FILE } from '../interceptor.js';
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
- else if (url === '/events' && method === 'GET') {
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
- else if (url === '/api/requests' && method === 'GET') {
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
- else {
169
- res.writeHead(404);
170
- res.end('Not Found');
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.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
- "@alenaksu/json-viewer": "^2.0.1",
44
- "marked": "^17.0.2"
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
  }