@myskyline_ai/ccdebug 0.2.1

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.
Files changed (61) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +129 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +674 -0
  6. package/dist/html-generator.d.ts +24 -0
  7. package/dist/html-generator.d.ts.map +1 -0
  8. package/dist/html-generator.js +141 -0
  9. package/dist/index-generator.d.ts +29 -0
  10. package/dist/index-generator.d.ts.map +1 -0
  11. package/dist/index-generator.js +271 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +28 -0
  15. package/dist/interceptor-loader.js +59 -0
  16. package/dist/interceptor.d.ts +46 -0
  17. package/dist/interceptor.d.ts.map +1 -0
  18. package/dist/interceptor.js +555 -0
  19. package/dist/log-file-manager.d.ts +15 -0
  20. package/dist/log-file-manager.d.ts.map +1 -0
  21. package/dist/log-file-manager.js +41 -0
  22. package/dist/shared-conversation-processor.d.ts +114 -0
  23. package/dist/shared-conversation-processor.d.ts.map +1 -0
  24. package/dist/shared-conversation-processor.js +663 -0
  25. package/dist/token-extractor.js +28 -0
  26. package/dist/types.d.ts +95 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +3 -0
  29. package/frontend/dist/index.global.js +1522 -0
  30. package/frontend/dist/styles.css +985 -0
  31. package/frontend/template.html +19 -0
  32. package/package.json +83 -0
  33. package/web/debug.html +14 -0
  34. package/web/dist/assets/index-BIP9r3RA.js +48 -0
  35. package/web/dist/assets/index-BIP9r3RA.js.map +1 -0
  36. package/web/dist/assets/index-De3gn-G-.css +1 -0
  37. package/web/dist/favicon.svg +4 -0
  38. package/web/dist/index.html +15 -0
  39. package/web/index.html +14 -0
  40. package/web/package.json +47 -0
  41. package/web/server/conversation-parser.d.ts +47 -0
  42. package/web/server/conversation-parser.d.ts.map +1 -0
  43. package/web/server/conversation-parser.js +564 -0
  44. package/web/server/conversation-parser.js.map +1 -0
  45. package/web/server/index.d.ts +16 -0
  46. package/web/server/index.d.ts.map +1 -0
  47. package/web/server/index.js +60 -0
  48. package/web/server/index.js.map +1 -0
  49. package/web/server/log-file-manager.d.ts +98 -0
  50. package/web/server/log-file-manager.d.ts.map +1 -0
  51. package/web/server/log-file-manager.js +512 -0
  52. package/web/server/log-file-manager.js.map +1 -0
  53. package/web/server/src/types/index.d.ts +68 -0
  54. package/web/server/src/types/index.d.ts.map +1 -0
  55. package/web/server/src/types/index.js +3 -0
  56. package/web/server/src/types/index.js.map +1 -0
  57. package/web/server/test-path.js +48 -0
  58. package/web/server/web-server.d.ts +41 -0
  59. package/web/server/web-server.d.ts.map +1 -0
  60. package/web/server/web-server.js +807 -0
  61. package/web/server/web-server.js.map +1 -0
@@ -0,0 +1,555 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClaudeTrafficLogger = void 0;
7
+ exports.initializeInterceptor = initializeInterceptor;
8
+ exports.getLogger = getLogger;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const html_generator_1 = require("./html-generator");
12
+ const log_file_manager_1 = require("./log-file-manager");
13
+ class ClaudeTrafficLogger {
14
+ constructor(config = {}) {
15
+ this.pendingRequests = new Map();
16
+ this.pairs = [];
17
+ this.config = {
18
+ logDirectory: ".claude-trace",
19
+ enableRealTimeHTML: false,
20
+ logLevel: "info",
21
+ ...config,
22
+ };
23
+ //创建.claude-trace目录
24
+ this.traceHomeDir = this.config.logDirectory;
25
+ if (!fs_1.default.existsSync(this.traceHomeDir)) {
26
+ fs_1.default.mkdirSync(this.traceHomeDir, { recursive: true });
27
+ }
28
+ //创建tracelog目录
29
+ this.traceLogDir = path_1.default.join(this.traceHomeDir, 'tracelog');
30
+ if (!fs_1.default.existsSync(this.traceLogDir)) {
31
+ fs_1.default.mkdirSync(this.traceLogDir, { recursive: true });
32
+ }
33
+ // 创建cclog目录
34
+ this.ccLogDir = path_1.default.join(this.traceHomeDir, 'cclog');
35
+ if (!fs_1.default.existsSync(this.ccLogDir)) {
36
+ fs_1.default.mkdirSync(this.ccLogDir, { recursive: true });
37
+ }
38
+ this.ccLogFile = '';
39
+ // Generate filenames based on custom name or timestamp
40
+ const logBaseName = config?.logBaseName || process.env.CLAUDE_TRACE_LOG_NAME;
41
+ const fileBaseName = logBaseName || `log-${new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, -5)}`; // Remove milliseconds and Z
42
+ this.traceLogFile = path_1.default.join(this.traceLogDir, `${fileBaseName}.jsonl`);
43
+ this.htmlFile = path_1.default.join(this.traceLogDir, `${fileBaseName}.html`);
44
+ // Initialize HTML generator
45
+ this.htmlGenerator = new html_generator_1.HTMLGenerator();
46
+ // Clear log file
47
+ fs_1.default.writeFileSync(this.traceLogFile, "");
48
+ // Output the actual filenames with absolute paths
49
+ console.log(`Logs will be written to:`);
50
+ console.log(` JSONL: ${path_1.default.resolve(this.traceLogFile)}`);
51
+ console.log(` HTML: ${path_1.default.resolve(this.htmlFile)}`);
52
+ }
53
+ isClaudeAPI(url) {
54
+ const urlString = typeof url === "string" ? url : url.toString();
55
+ const includeAllRequests = process.env.CLAUDE_TRACE_INCLUDE_ALL_REQUESTS === "true";
56
+ // Support custom ANTHROPIC_BASE_URL
57
+ const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
58
+ const apiHost = new URL(baseUrl).hostname;
59
+ // Check for direct Anthropic API calls
60
+ const isAnthropicAPI = urlString.includes(apiHost);
61
+ // Check for AWS Bedrock Claude API calls
62
+ const isBedrockAPI = urlString.includes("bedrock-runtime.") && urlString.includes(".amazonaws.com");
63
+ if (includeAllRequests) {
64
+ return isAnthropicAPI || isBedrockAPI; // Capture all Claude API requests
65
+ }
66
+ return (isAnthropicAPI && urlString.includes("/v1/messages")) || isBedrockAPI;
67
+ }
68
+ generateRequestId() {
69
+ return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
70
+ }
71
+ redactSensitiveHeaders(headers) {
72
+ const redactedHeaders = { ...headers };
73
+ const sensitiveKeys = [
74
+ "authorization",
75
+ "x-api-key",
76
+ "x-auth-token",
77
+ "cookie",
78
+ "set-cookie",
79
+ "x-session-token",
80
+ "x-access-token",
81
+ "bearer",
82
+ "proxy-authorization",
83
+ ];
84
+ for (const key of Object.keys(redactedHeaders)) {
85
+ const lowerKey = key.toLowerCase();
86
+ if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
87
+ // Keep first 10 chars and last 4 chars, redact middle
88
+ const value = redactedHeaders[key];
89
+ if (value && value.length > 14) {
90
+ redactedHeaders[key] = `${value.substring(0, 10)}...${value.slice(-4)}`;
91
+ }
92
+ else if (value && value.length > 4) {
93
+ redactedHeaders[key] = `${value.substring(0, 2)}...${value.slice(-2)}`;
94
+ }
95
+ else {
96
+ redactedHeaders[key] = "[REDACTED]";
97
+ }
98
+ }
99
+ }
100
+ return redactedHeaders;
101
+ }
102
+ async cloneResponse(response) {
103
+ // Clone the response to avoid consuming the body
104
+ return response.clone();
105
+ }
106
+ async parseRequestBody(body) {
107
+ if (!body)
108
+ return null;
109
+ if (typeof body === "string") {
110
+ try {
111
+ return JSON.parse(body);
112
+ }
113
+ catch {
114
+ return body;
115
+ }
116
+ }
117
+ if (body instanceof FormData) {
118
+ const formObject = {};
119
+ for (const [key, value] of body.entries()) {
120
+ formObject[key] = value;
121
+ }
122
+ return formObject;
123
+ }
124
+ return body;
125
+ }
126
+ async parseResponseBody(response) {
127
+ const contentType = response.headers.get("content-type") || "";
128
+ try {
129
+ if (contentType.includes("application/json")) {
130
+ const body = await response.json();
131
+ return { body };
132
+ }
133
+ else if (contentType.includes("text/event-stream")) {
134
+ const body_raw = await response.text();
135
+ return { body_raw };
136
+ }
137
+ else if (contentType.includes("text/")) {
138
+ const body_raw = await response.text();
139
+ return { body_raw };
140
+ }
141
+ else {
142
+ // For other types, try to read as text
143
+ const body_raw = await response.text();
144
+ return { body_raw };
145
+ }
146
+ }
147
+ catch (error) {
148
+ // Silent error handling during runtime
149
+ return {};
150
+ }
151
+ }
152
+ instrumentAll() {
153
+ this.instrumentFetch();
154
+ this.instrumentNodeHTTP();
155
+ }
156
+ instrumentFetch() {
157
+ if (!global.fetch) {
158
+ // Silent - fetch not available
159
+ return;
160
+ }
161
+ // Check if already instrumented by checking for our marker
162
+ if (global.fetch.__claudeTraceInstrumented) {
163
+ return;
164
+ }
165
+ const originalFetch = global.fetch;
166
+ const logger = this;
167
+ global.fetch = async function (input, init = {}) {
168
+ // Convert input to URL for consistency
169
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
170
+ // Only intercept Claude API calls
171
+ if (!logger.isClaudeAPI(url)) {
172
+ return originalFetch(input, init);
173
+ }
174
+ const requestId = logger.generateRequestId();
175
+ const requestTimestamp = Date.now();
176
+ // Capture request details
177
+ const requestData = {
178
+ timestamp: requestTimestamp / 1000, // Convert to seconds (like Python version)
179
+ method: init.method || "GET",
180
+ url: url,
181
+ headers: logger.redactSensitiveHeaders(Object.fromEntries(new Headers(init.headers || {}).entries())),
182
+ body: await logger.parseRequestBody(init.body),
183
+ };
184
+ // Store pending request
185
+ logger.pendingRequests.set(requestId, requestData);
186
+ try {
187
+ // Make the actual request
188
+ const response = await originalFetch(input, init);
189
+ const responseTimestamp = Date.now();
190
+ // Clone response to avoid consuming the body
191
+ const clonedResponse = await logger.cloneResponse(response);
192
+ // Parse response body
193
+ const responseBodyData = await logger.parseResponseBody(clonedResponse);
194
+ // Create response data
195
+ const responseData = {
196
+ timestamp: responseTimestamp / 1000,
197
+ status_code: response.status,
198
+ headers: logger.redactSensitiveHeaders(Object.fromEntries(response.headers.entries())),
199
+ ...responseBodyData,
200
+ };
201
+ // Create paired request-response object
202
+ const pair = {
203
+ request: requestData,
204
+ response: responseData,
205
+ logged_at: new Date().toISOString(),
206
+ };
207
+ // Remove from pending and add to pairs
208
+ logger.pendingRequests.delete(requestId);
209
+ logger.pairs.push(pair);
210
+ // Write to log file
211
+ await logger.writePairToLog(pair);
212
+ // Generate HTML if enabled
213
+ if (logger.config.enableRealTimeHTML) {
214
+ await logger.generateHTML();
215
+ }
216
+ return response;
217
+ }
218
+ catch (error) {
219
+ // Remove from pending requests on error
220
+ logger.pendingRequests.delete(requestId);
221
+ throw error;
222
+ }
223
+ };
224
+ // Mark fetch as instrumented
225
+ global.fetch.__claudeTraceInstrumented = true;
226
+ // Silent initialization
227
+ }
228
+ instrumentNodeHTTP() {
229
+ try {
230
+ const http = require("http");
231
+ const https = require("https");
232
+ const logger = this;
233
+ // Instrument http.request
234
+ if (http.request && !http.request.__claudeTraceInstrumented) {
235
+ const originalHttpRequest = http.request;
236
+ http.request = function (options, callback) {
237
+ return logger.interceptNodeRequest(originalHttpRequest, options, callback, false);
238
+ };
239
+ http.request.__claudeTraceInstrumented = true;
240
+ }
241
+ // Instrument http.get
242
+ if (http.get && !http.get.__claudeTraceInstrumented) {
243
+ const originalHttpGet = http.get;
244
+ http.get = function (options, callback) {
245
+ return logger.interceptNodeRequest(originalHttpGet, options, callback, false);
246
+ };
247
+ http.get.__claudeTraceInstrumented = true;
248
+ }
249
+ // Instrument https.request
250
+ if (https.request && !https.request.__claudeTraceInstrumented) {
251
+ const originalHttpsRequest = https.request;
252
+ https.request = function (options, callback) {
253
+ return logger.interceptNodeRequest(originalHttpsRequest, options, callback, true);
254
+ };
255
+ https.request.__claudeTraceInstrumented = true;
256
+ }
257
+ // Instrument https.get
258
+ if (https.get && !https.get.__claudeTraceInstrumented) {
259
+ const originalHttpsGet = https.get;
260
+ https.get = function (options, callback) {
261
+ return logger.interceptNodeRequest(originalHttpsGet, options, callback, true);
262
+ };
263
+ https.get.__claudeTraceInstrumented = true;
264
+ }
265
+ }
266
+ catch (error) {
267
+ // Silent error handling
268
+ }
269
+ }
270
+ interceptNodeRequest(originalRequest, options, callback, isHttps) {
271
+ // Parse URL from options
272
+ const url = this.parseNodeRequestURL(options, isHttps);
273
+ if (!this.isClaudeAPI(url)) {
274
+ return originalRequest.call(this, options, callback);
275
+ }
276
+ const requestId = this.generateRequestId();
277
+ const requestTimestamp = Date.now();
278
+ let requestBody = "";
279
+ // Create the request
280
+ const req = originalRequest.call(this, options, (res) => {
281
+ const responseTimestamp = Date.now();
282
+ let responseBody = "";
283
+ // Capture response data
284
+ res.on("data", (chunk) => {
285
+ responseBody += chunk;
286
+ });
287
+ res.on("end", async () => {
288
+ // Process the captured request/response
289
+ const requestData = {
290
+ timestamp: requestTimestamp / 1000,
291
+ method: options.method || "GET",
292
+ url: url,
293
+ headers: this.redactSensitiveHeaders(options.headers || {}),
294
+ body: requestBody ? await this.parseRequestBody(requestBody) : null,
295
+ };
296
+ const responseData = {
297
+ timestamp: responseTimestamp / 1000,
298
+ status_code: res.statusCode,
299
+ headers: this.redactSensitiveHeaders(res.headers || {}),
300
+ ...(await this.parseResponseBodyFromString(responseBody, res.headers["content-type"])),
301
+ };
302
+ const pair = {
303
+ request: requestData,
304
+ response: responseData,
305
+ logged_at: new Date().toISOString(),
306
+ };
307
+ this.pairs.push(pair);
308
+ await this.writePairToLog(pair);
309
+ if (this.config.enableRealTimeHTML) {
310
+ await this.generateHTML();
311
+ }
312
+ });
313
+ // Call original callback if provided
314
+ if (callback) {
315
+ callback(res);
316
+ }
317
+ });
318
+ // Capture request body
319
+ const originalWrite = req.write;
320
+ req.write = function (chunk) {
321
+ if (chunk) {
322
+ requestBody += chunk;
323
+ }
324
+ return originalWrite.call(this, chunk);
325
+ };
326
+ return req;
327
+ }
328
+ parseNodeRequestURL(options, isHttps) {
329
+ if (typeof options === "string") {
330
+ return options;
331
+ }
332
+ const protocol = isHttps ? "https:" : "http:";
333
+ const hostname = options.hostname || options.host || "localhost";
334
+ const port = options.port ? `:${options.port}` : "";
335
+ const path = options.path || "/";
336
+ return `${protocol}//${hostname}${port}${path}`;
337
+ }
338
+ async parseResponseBodyFromString(body, contentType) {
339
+ try {
340
+ if (contentType && contentType.includes("application/json")) {
341
+ return { body: JSON.parse(body) };
342
+ }
343
+ else if (contentType && contentType.includes("text/event-stream")) {
344
+ return { body_raw: body };
345
+ }
346
+ else {
347
+ return { body_raw: body };
348
+ }
349
+ }
350
+ catch (error) {
351
+ return { body_raw: body };
352
+ }
353
+ }
354
+ async writePairToLog(pair) {
355
+ try {
356
+ const jsonLine = JSON.stringify(pair) + "\n";
357
+ fs_1.default.appendFileSync(this.traceLogFile, jsonLine);
358
+ }
359
+ catch (error) {
360
+ // Silent error handling during runtime
361
+ }
362
+ }
363
+ async generateHTML() {
364
+ try {
365
+ const includeAllRequests = process.env.CLAUDE_TRACE_INCLUDE_ALL_REQUESTS === "true";
366
+ await this.htmlGenerator.generateHTML(this.pairs, this.htmlFile, {
367
+ title: `${this.pairs.length} API Calls`,
368
+ timestamp: new Date().toISOString().replace("T", " ").slice(0, -5),
369
+ includeAllRequests,
370
+ });
371
+ // Silent HTML generation
372
+ }
373
+ catch (error) {
374
+ // Silent error handling during runtime
375
+ }
376
+ }
377
+ cleanup() {
378
+ console.log("Cleaning up orphaned requests...");
379
+ for (const [, requestData] of this.pendingRequests.entries()) {
380
+ const orphanedPair = {
381
+ request: requestData,
382
+ response: null,
383
+ note: "ORPHANED_REQUEST - No matching response received",
384
+ logged_at: new Date().toISOString(),
385
+ };
386
+ try {
387
+ const jsonLine = JSON.stringify(orphanedPair) + "\n";
388
+ fs_1.default.appendFileSync(this.traceLogFile, jsonLine);
389
+ }
390
+ catch (error) {
391
+ console.log(`Error writing orphaned request: ${error}`);
392
+ }
393
+ }
394
+ this.pendingRequests.clear();
395
+ console.log(`Cleanup complete. Logged ${this.pairs.length} pairs`);
396
+ //获取cc会话的sessionid
397
+ let sessionId = this.getSessionIdFromLog();
398
+ if (sessionId != '') {
399
+ //将当前会话对应的cc日志文件,拷贝到.claude-trace/cclog目录
400
+ this.copyCClogFile(sessionId);
401
+ // Rename log file based on sessionid from first record
402
+ this.renameTraceLogFileBySessionId(sessionId);
403
+ }
404
+ // Open browser if requested
405
+ // const shouldOpenBrowser = process.env.CLAUDE_TRACE_OPEN_BROWSER === "true";
406
+ // if (shouldOpenBrowser && fs.existsSync(this.htmlFile)) {
407
+ // try {
408
+ // spawn("open", [this.htmlFile], { detached: true, stdio: "ignore" }).unref();
409
+ // console.log(`Opening ${this.htmlFile} in browser`);
410
+ // } catch (error) {
411
+ // console.log(`Failed to open browser: ${error}`);
412
+ // }
413
+ // }
414
+ }
415
+ getSessionIdFromLog() {
416
+ // Check if log file exists
417
+ if (!fs_1.default.existsSync(this.traceLogFile)) {
418
+ console.log("获取sessionId错误:Log file does not exist");
419
+ return '';
420
+ }
421
+ // Read the first line of the JSONL file
422
+ const fileContent = fs_1.default.readFileSync(this.traceLogFile, 'utf-8');
423
+ const lines = fileContent.split('\n').filter(line => line.trim());
424
+ if (lines.length === 0) {
425
+ console.log("获取sessionId错误:Log file is empty");
426
+ return '';
427
+ }
428
+ // 循环读取日志,直到找到user_id为止
429
+ let userId = null;
430
+ for (const line of lines) {
431
+ const record = JSON.parse(line);
432
+ userId = record?.request?.body?.metadata?.user_id;
433
+ if (userId) {
434
+ break;
435
+ }
436
+ }
437
+ if (!userId) {
438
+ console.log("获取sessionId错误:No user_id found in any record");
439
+ return '';
440
+ }
441
+ // Extract sessionid from user_id (format: xxxx_session_{sessionid})
442
+ const sessionMatch = userId.match(/_session_([^_]+)$/);
443
+ if (!sessionMatch || !sessionMatch[1]) {
444
+ console.log(`获取sessionId错误:No sessionid found in user_id: ${userId}`);
445
+ return '';
446
+ }
447
+ return sessionMatch[1];
448
+ }
449
+ copyCClogFile(sessionId) {
450
+ //将当前会话对应的cc日志文件,拷贝到.claude-trace/cclog目录
451
+ try {
452
+ // 创建LogFileManager实例
453
+ const logFileManager = new log_file_manager_1.LogFileManager();
454
+ // 获取当前工作目录作为项目路径
455
+ const currentProjectPath = process.cwd();
456
+ // 通过LogFileManager解析源日志目录
457
+ const sourceLogDir = logFileManager.resolveLogDirectory(currentProjectPath);
458
+ // 构建源文件路径(假设源文件名为sessionId.jsonl)
459
+ const sourceFile = path_1.default.join(sourceLogDir, `${sessionId}.jsonl`);
460
+ // 构建目标文件路径
461
+ this.ccLogFile = path_1.default.join(this.ccLogDir, `${sessionId}.jsonl`);
462
+ // 检查源文件是否存在
463
+ if (!fs_1.default.existsSync(sourceFile)) {
464
+ console.log(`源CC日志文件不存在: ${sourceFile}`);
465
+ return;
466
+ }
467
+ // 拷贝文件
468
+ fs_1.default.copyFileSync(sourceFile, this.ccLogFile);
469
+ console.log(`CC日志文件已从 ${sourceFile} 拷贝到 ${this.ccLogFile}`);
470
+ // 读取sourceLogDir目录下所有agent_*.jsonl文件,读取第一条记录的sessionId,找到与sessionId变量值相同的文件,拷贝到ccLogDir目录
471
+ const files = fs_1.default.readdirSync(sourceLogDir).filter(file => file.startsWith('agent-') && file.endsWith('.jsonl'));
472
+ for (const file of files) {
473
+ const filePath = path_1.default.join(sourceLogDir, file);
474
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
475
+ const lines = content.split('\n').filter(line => line.trim());
476
+ if (lines.length > 0) {
477
+ try {
478
+ const firstRecord = JSON.parse(lines[0]);
479
+ const recordSessionId = firstRecord?.sessionId;
480
+ if (recordSessionId === sessionId) {
481
+ // 构建目标文件路径
482
+ const ccAgentLogFile = path_1.default.join(this.ccLogDir, file);
483
+ // 拷贝文件
484
+ fs_1.default.copyFileSync(filePath, ccAgentLogFile);
485
+ console.log(`SubAgent的CC日志文件已从 ${filePath} 拷贝到 ${ccAgentLogFile}`);
486
+ }
487
+ }
488
+ catch (parseError) {
489
+ // 静默处理解析错误,继续下一个文件
490
+ continue;
491
+ }
492
+ }
493
+ }
494
+ }
495
+ catch (error) {
496
+ console.log(`拷贝CC日志文件时出错: ${error}`);
497
+ }
498
+ }
499
+ renameTraceLogFileBySessionId(sessionId) {
500
+ try {
501
+ const logDir = path_1.default.dirname(this.traceLogFile);
502
+ const newLogFile = path_1.default.join(logDir, `${sessionId}.jsonl`);
503
+ // Rename the file
504
+ fs_1.default.renameSync(this.traceLogFile, newLogFile);
505
+ console.log(`Log file renamed from ${path_1.default.basename(this.traceLogFile)} to ${sessionId}.jsonl`);
506
+ // Update the logFile path for future reference
507
+ this.traceLogFile = newLogFile;
508
+ }
509
+ catch (error) {
510
+ console.log(`Error renaming log file: ${error}`);
511
+ }
512
+ }
513
+ getStats() {
514
+ return {
515
+ totalPairs: this.pairs.length,
516
+ pendingRequests: this.pendingRequests.size,
517
+ logFile: this.traceLogFile,
518
+ htmlFile: this.htmlFile,
519
+ };
520
+ }
521
+ }
522
+ exports.ClaudeTrafficLogger = ClaudeTrafficLogger;
523
+ // Global logger instance
524
+ let globalLogger = null;
525
+ // Track if event listeners have been set up
526
+ let eventListenersSetup = false;
527
+ function initializeInterceptor(config) {
528
+ if (globalLogger) {
529
+ console.warn("Interceptor already initialized");
530
+ return globalLogger;
531
+ }
532
+ globalLogger = new ClaudeTrafficLogger(config);
533
+ globalLogger.instrumentAll();
534
+ // Setup cleanup on process exit only once
535
+ if (!eventListenersSetup) {
536
+ const cleanup = () => {
537
+ if (globalLogger) {
538
+ globalLogger.cleanup();
539
+ }
540
+ };
541
+ process.on("exit", cleanup);
542
+ process.on("SIGINT", cleanup);
543
+ process.on("SIGTERM", cleanup);
544
+ process.on("uncaughtException", (error) => {
545
+ console.error("Uncaught exception:", error);
546
+ cleanup();
547
+ process.exit(1);
548
+ });
549
+ eventListenersSetup = true;
550
+ }
551
+ return globalLogger;
552
+ }
553
+ function getLogger() {
554
+ return globalLogger;
555
+ }
@@ -0,0 +1,15 @@
1
+ export declare class LogFileManager {
2
+ private logDir;
3
+ private fileWatcher;
4
+ constructor(logDir?: string);
5
+ private getUserHome;
6
+ private getClaudeProjectsDir;
7
+ /**
8
+ * 根据项目路径解析对应的日志目录
9
+ * @param projectPath 项目路径
10
+ * @returns 日志目录路径
11
+ */
12
+ resolveLogDirectory(projectPath: string): string;
13
+ private generateProjectId;
14
+ }
15
+ //# sourceMappingURL=log-file-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-file-manager.d.ts","sourceRoot":"","sources":["../src/log-file-manager.ts"],"names":[],"mappings":"AAIA,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAa;gBAEpB,MAAM,CAAC,EAAE,MAAM;IAI3B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,oBAAoB;IAI5B;;;;OAIG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAUhD,OAAO,CAAC,iBAAiB;CAQ1B"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogFileManager = void 0;
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ class LogFileManager {
8
+ constructor(logDir) {
9
+ this.fileWatcher = null;
10
+ this.logDir = logDir || '';
11
+ }
12
+ getUserHome() {
13
+ return os.homedir();
14
+ }
15
+ getClaudeProjectsDir() {
16
+ return path.join(this.getUserHome(), '.claude', 'projects');
17
+ }
18
+ /**
19
+ * 根据项目路径解析对应的日志目录
20
+ * @param projectPath 项目路径
21
+ * @returns 日志目录路径
22
+ */
23
+ resolveLogDirectory(projectPath) {
24
+ // 1. 获取项目目录的绝对路径
25
+ const absoluteProjectPath = path.resolve(projectPath);
26
+ // 2. 根据实际规律生成项目目录标识
27
+ const projectId = this.generateProjectId(absoluteProjectPath);
28
+ // 3. 构建对应的日志目录路径
29
+ const logDir = path.join(this.getClaudeProjectsDir(), projectId);
30
+ return logDir;
31
+ }
32
+ generateProjectId(projectPath) {
33
+ // 根据实际规律:将路径中的所有 '/'、'\'、'_'、':' 和空格替换为 '-',同时将非 ASCII 字符(如中文)也替换为 '-'
34
+ // 例如:"/Users/ligf/Code/claude-code/ccdebug/ccdemo" -> "-Users-ligf-Code-claude-code-ccdebug-ccdemo"
35
+ // 例如:"/Users/ligf/Code/项目/测试" -> "-Users-ligf-Code------"
36
+ // 例如:"D:\mysoft\CC客服" -> "-D--mysoft-CC--"
37
+ // 例如:"C:\Program Files\MyApp" -> "-C--Program-Files-MyApp"
38
+ return projectPath.replace(/[\/\\_:\s]/g, '-').replace(/[^\x00-\x7F]/g, '-');
39
+ }
40
+ }
41
+ exports.LogFileManager = LogFileManager;