cc-viewer 1.2.7 → 1.2.9
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 +46 -28
- package/cli.js +43 -102
- package/dist/assets/{index-CN78DISW.js → index-Do8E8Nxd.js} +110 -110
- package/dist/index.html +1 -1
- package/findcc.js +107 -0
- package/i18n.js +2140 -6
- package/interceptor.js +2 -3
- package/package.json +2 -1
- package/proxy.js +2 -110
- package/server.js +1 -1
- package/locales/i18n.json +0 -2139
package/interceptor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { appendFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync, st
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { dirname, join, basename } from 'node:path';
|
|
7
|
+
import { LOG_DIR } from './findcc.js';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,7 +31,7 @@ function generateNewLogFilePath() {
|
|
|
30
31
|
let cwd;
|
|
31
32
|
try { cwd = process.cwd(); } catch { cwd = homedir(); }
|
|
32
33
|
const projectName = basename(cwd).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
|
|
33
|
-
const dir = join(
|
|
34
|
+
const dir = join(LOG_DIR, projectName);
|
|
34
35
|
try { mkdirSync(dir, { recursive: true }); } catch { }
|
|
35
36
|
return { filePath: join(dir, `${projectName}_${ts}.jsonl`), dir, projectName };
|
|
36
37
|
}
|
|
@@ -241,8 +242,6 @@ function isAnthropicApiPath(urlStr) {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
// 不再需要折叠函数,保存完整 JSON 供前端渲染
|
|
245
|
-
|
|
246
245
|
// 组装流式消息为完整的 message 对象
|
|
247
246
|
function assembleStreamMessage(events) {
|
|
248
247
|
let message = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-viewer",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Claude Code Logger visualization management tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "server.js",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"proxy.js",
|
|
48
48
|
"interceptor.js",
|
|
49
49
|
"i18n.js",
|
|
50
|
+
"findcc.js",
|
|
50
51
|
"locales/",
|
|
51
52
|
"concepts/"
|
|
52
53
|
],
|
package/proxy.js
CHANGED
|
@@ -34,13 +34,7 @@ function getOriginalBaseUrl() {
|
|
|
34
34
|
export function startProxy() {
|
|
35
35
|
return new Promise((resolve, reject) => {
|
|
36
36
|
const server = createServer(async (req, res) => {
|
|
37
|
-
// [Debug] Log incoming request
|
|
38
|
-
// console.error(`[CC-Viewer Proxy] Received request: ${req.method} ${req.url}`);
|
|
39
|
-
|
|
40
|
-
// Handle CORS preflight if needed (though claude cli probably doesn't send OPTIONS)
|
|
41
|
-
|
|
42
37
|
const originalBaseUrl = getOriginalBaseUrl();
|
|
43
|
-
const targetUrl = new URL(req.url, originalBaseUrl);
|
|
44
38
|
|
|
45
39
|
// Use the patched fetch (which logs to cc-viewer)
|
|
46
40
|
try {
|
|
@@ -48,25 +42,12 @@ export function startProxy() {
|
|
|
48
42
|
const headers = { ...req.headers };
|
|
49
43
|
delete headers.host; // Let fetch set the host
|
|
50
44
|
|
|
51
|
-
// [Fix] Handle compressed body
|
|
52
|
-
// If content-encoding is set (e.g. gzip), and we read the raw stream into a buffer,
|
|
53
|
-
// we are passing the compressed buffer as body.
|
|
54
|
-
// fetch will automatically add content-length, but might not handle content-encoding correctly if we just pass the buffer?
|
|
55
|
-
// Actually, fetch should handle it fine if we pass the headers.
|
|
56
|
-
|
|
57
|
-
// However, if we are reading the body here to pass it to fetch, we are buffering it.
|
|
58
|
-
// For large uploads this might be bad, but for text prompts it's fine.
|
|
59
|
-
|
|
60
|
-
// We need to read the body if any
|
|
61
45
|
const buffers = [];
|
|
62
46
|
for await (const chunk of req) {
|
|
63
47
|
buffers.push(chunk);
|
|
64
48
|
}
|
|
65
49
|
const body = Buffer.concat(buffers);
|
|
66
50
|
|
|
67
|
-
// [Debug] Log body size
|
|
68
|
-
// console.error(`[CC-Viewer Proxy] Request body size: ${body.length}`);
|
|
69
|
-
|
|
70
51
|
const fetchOptions = {
|
|
71
52
|
method: req.method,
|
|
72
53
|
headers: headers,
|
|
@@ -80,94 +61,14 @@ export function startProxy() {
|
|
|
80
61
|
fetchOptions.body = body;
|
|
81
62
|
}
|
|
82
63
|
|
|
83
|
-
//
|
|
84
|
-
// If originalBaseUrl is also a proxy or special endpoint, make sure we construct the full URL correctly.
|
|
85
|
-
// originalBaseUrl might end with /v1 or not.
|
|
86
|
-
// req.url from proxy server is usually just the path (e.g. /v1/messages) if client is configured with base_url=http://localhost:port
|
|
87
|
-
// So new URL(req.url, originalBaseUrl) should work.
|
|
88
|
-
|
|
89
|
-
// However, if originalBaseUrl already contains a path (e.g. /api/anthropic), and req.url is /v1/messages,
|
|
90
|
-
// new URL(req.url, originalBaseUrl) might treat req.url as absolute path if it starts with /, replacing the path in originalBaseUrl?
|
|
91
|
-
// Let's test: new URL('/v1/messages', 'https://example.com/api').toString() -> 'https://example.com/v1/messages' (path replaced!)
|
|
92
|
-
|
|
93
|
-
// This is why we get 404! The user's base URL is https://antchat.alipay.com/api/anthropic
|
|
94
|
-
// But our proxy constructs: https://antchat.alipay.com/v1/messages
|
|
95
|
-
// It lost the /api/anthropic part!
|
|
96
|
-
|
|
97
|
-
// We need to append req.url to the pathname of originalBaseUrl, carefully avoiding double slashes.
|
|
98
|
-
|
|
99
|
-
const originalUrlObj = new URL(originalBaseUrl);
|
|
100
|
-
// Ensure original pathname doesn't end with slash if we append
|
|
101
|
-
let basePath = originalUrlObj.pathname;
|
|
102
|
-
if (basePath.endsWith('/')) basePath = basePath.slice(0, -1);
|
|
103
|
-
|
|
104
|
-
// req.url starts with / usually
|
|
105
|
-
const reqPath = req.url.startsWith('/') ? req.url : '/' + req.url;
|
|
106
|
-
|
|
107
|
-
// Check if we should join them
|
|
108
|
-
// If req.url is /v1/messages, and base is /api/anthropic, we want /api/anthropic/v1/messages
|
|
109
|
-
originalUrlObj.pathname = basePath + reqPath;
|
|
110
|
-
// Search params are already in req.url? Yes, req.url includes query string in Node http server.
|
|
111
|
-
// Wait, new URL(req.url, base) parses query string correctly.
|
|
112
|
-
// But if we manually concat pathname, we need to handle query string separately.
|
|
113
|
-
|
|
114
|
-
// Let's do it simpler: use string concatenation for the full URL
|
|
115
|
-
// But we need to handle the origin correctly.
|
|
116
|
-
|
|
117
|
-
// Better approach:
|
|
118
|
-
// 1. Remove trailing slash from originalBaseUrl
|
|
64
|
+
// 拼接完整 URL,保留 originalBaseUrl 中的路径前缀
|
|
119
65
|
const cleanBase = originalBaseUrl.endsWith('/') ? originalBaseUrl.slice(0, -1) : originalBaseUrl;
|
|
120
|
-
// 2. Remove leading slash from req.url
|
|
121
66
|
const cleanReq = req.url.startsWith('/') ? req.url.slice(1) : req.url;
|
|
122
|
-
// 3. Join
|
|
123
67
|
const fullUrl = `${cleanBase}/${cleanReq}`;
|
|
124
68
|
|
|
125
|
-
// [Debug] Proxying to
|
|
126
|
-
// console.error(`[CC-Viewer Proxy] Forwarding to: ${fullUrl}`);
|
|
127
|
-
|
|
128
69
|
const response = await fetch(fullUrl, fetchOptions);
|
|
129
70
|
|
|
130
|
-
//
|
|
131
|
-
// Handle decompression manually if needed.
|
|
132
|
-
// Node's fetch automatically decompresses if 'compress: true' is default?
|
|
133
|
-
// Actually, fetch handles gzip/deflate by default.
|
|
134
|
-
// But if we pipe the response body to the client response (res), we need to be careful.
|
|
135
|
-
// The issue is likely that we are trying to read the body as text/json for logging,
|
|
136
|
-
// but it might be compressed or binary.
|
|
137
|
-
|
|
138
|
-
// Let's modify how we handle the response body.
|
|
139
|
-
// We need to:
|
|
140
|
-
// 1. Pipe the response to the client (res) so Claude Code gets the data.
|
|
141
|
-
// 2. Clone the response to read it for logging? fetch response.clone() might not work with streaming body easily.
|
|
142
|
-
|
|
143
|
-
// Better approach: intercept the stream.
|
|
144
|
-
// Or simply: don't log the response body for now to avoid breaking the stream.
|
|
145
|
-
// User just wants to see the request in the viewer.
|
|
146
|
-
// If we want to log response, we need to handle it carefully.
|
|
147
|
-
|
|
148
|
-
// Let's check where ZlibError comes from. It likely comes from `response.text()` or `response.json()`
|
|
149
|
-
// if the content-encoding header is set but fetch didn't decompress it automatically?
|
|
150
|
-
// Or maybe we are double decompressing?
|
|
151
|
-
|
|
152
|
-
// Wait, if we use `response.body.pipe(res)`, that's fine.
|
|
153
|
-
// But do we read the body elsewhere?
|
|
154
|
-
|
|
155
|
-
// Let's look at how we log the response.
|
|
156
|
-
// We are not logging response body in this proxy.js currently.
|
|
157
|
-
// Wait, line 105: `response.body.pipe(res);`
|
|
158
|
-
|
|
159
|
-
// If the error happens, it might be because `fetch` failed to decompress?
|
|
160
|
-
// Or maybe `response.body` is already decompressed stream, but we are piping it to `res` which expects raw?
|
|
161
|
-
// No, `res` (http.ServerResponse) expects raw data.
|
|
162
|
-
|
|
163
|
-
// If `fetch` decompresses automatically, then `response.body` yields decompressed chunks.
|
|
164
|
-
// But `res` writes those chunks to the client.
|
|
165
|
-
// The client (Claude Code) expects compressed data if it sent `Accept-Encoding: gzip`.
|
|
166
|
-
// If we send decompressed data but keep `Content-Encoding: gzip` header, client will try to decompress again -> ZlibError!
|
|
167
|
-
|
|
168
|
-
// FIX: Remove content-encoding header from response headers before piping to client.
|
|
169
|
-
// This tells the client "the data I'm sending you is NOT compressed" (because fetch already decompressed it).
|
|
170
|
-
|
|
71
|
+
// fetch 自动解压,需移除编码相关 header 避免客户端重复解压
|
|
171
72
|
const responseHeaders = {};
|
|
172
73
|
for (const [key, value] of response.headers.entries()) {
|
|
173
74
|
// Skip Content-Encoding and Transfer-Encoding to let Node/Client handle it
|
|
@@ -178,20 +79,11 @@ export function startProxy() {
|
|
|
178
79
|
|
|
179
80
|
res.writeHead(response.status, responseHeaders);
|
|
180
81
|
|
|
181
|
-
// Also log that we are piping
|
|
182
|
-
// console.error(`[CC-Viewer Proxy] Response status: ${response.status}`);
|
|
183
|
-
|
|
184
82
|
if (response.body) {
|
|
185
|
-
// We need to convert Web Stream (response.body) to Node Stream for piping to res
|
|
186
|
-
// Node 18+ fetch returns a Web ReadableStream.
|
|
187
|
-
// We can use Readable.fromWeb(response.body)
|
|
188
83
|
const { Readable } = await import('node:stream');
|
|
189
84
|
// @ts-ignore
|
|
190
85
|
const nodeStream = Readable.fromWeb(response.body);
|
|
191
86
|
nodeStream.pipe(res);
|
|
192
|
-
|
|
193
|
-
// Optional: Log response body for debugging (careful with streams)
|
|
194
|
-
// For now, let's just ensure reliability.
|
|
195
87
|
} else {
|
|
196
88
|
res.end();
|
|
197
89
|
}
|
package/server.js
CHANGED
|
@@ -5,9 +5,9 @@ import { dirname, join, extname, basename } from 'node:path';
|
|
|
5
5
|
import { homedir, userInfo, platform } from 'node:os';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
7
7
|
import { LOG_FILE, _initPromise, _resumeState, resolveResumeChoice, _projectName, _cachedApiKey, _cachedAuthHeader, _cachedHaikuModel } from './interceptor.js';
|
|
8
|
+
import { LOG_DIR } from './findcc.js';
|
|
8
9
|
import { t, detectLanguage } from './i18n.js';
|
|
9
10
|
|
|
10
|
-
const LOG_DIR = join(homedir(), '.claude', 'cc-viewer');
|
|
11
11
|
const PREFS_FILE = join(LOG_DIR, 'preferences.json');
|
|
12
12
|
|
|
13
13
|
|