@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,24 @@
1
+ import { RawPair } from "./types";
2
+ export declare class HTMLGenerator {
3
+ private frontendDir;
4
+ private templatePath;
5
+ private bundlePath;
6
+ constructor();
7
+ private ensureFrontendBuilt;
8
+ private loadTemplateFiles;
9
+ private filterClaudeAPIPairs;
10
+ private filterShortConversations;
11
+ private prepareDataForInjection;
12
+ private escapeHtml;
13
+ generateHTML(pairs: RawPair[], outputFile: string, options?: {
14
+ title?: string;
15
+ timestamp?: string;
16
+ includeAllRequests?: boolean;
17
+ }): Promise<void>;
18
+ generateHTMLFromJSONL(jsonlFile: string, outputFile?: string, includeAllRequests?: boolean): Promise<string>;
19
+ getTemplatePaths(): {
20
+ templatePath: string;
21
+ bundlePath: string;
22
+ };
23
+ }
24
+ //# sourceMappingURL=html-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-generator.d.ts","sourceRoot":"","sources":["../src/html-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAkC,MAAM,SAAS,CAAC;AAElE,qBAAa,aAAa;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAS;;IAQ3B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,UAAU;IASL,YAAY,CACxB,KAAK,EAAE,OAAO,EAAE,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;KACxB,GACJ,OAAO,CAAC,IAAI,CAAC;IAoDH,qBAAqB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,kBAAkB,GAAE,OAAc,GAChC,OAAO,CAAC,MAAM,CAAC;IAoCX,gBAAgB,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAMvE"}
@@ -0,0 +1,141 @@
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.HTMLGenerator = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class HTMLGenerator {
10
+ constructor() {
11
+ this.frontendDir = path_1.default.join(__dirname, "..", "frontend");
12
+ this.templatePath = path_1.default.join(this.frontendDir, "template.html");
13
+ this.bundlePath = path_1.default.join(this.frontendDir, "dist", "index.global.js");
14
+ }
15
+ ensureFrontendBuilt() {
16
+ if (!fs_1.default.existsSync(this.bundlePath)) {
17
+ throw new Error(`Frontend bundle not found at ${this.bundlePath}. ` + `Run 'npm run build' in frontend directory first.`);
18
+ }
19
+ }
20
+ loadTemplateFiles() {
21
+ this.ensureFrontendBuilt();
22
+ const htmlTemplate = fs_1.default.readFileSync(this.templatePath, "utf-8");
23
+ const jsBundle = fs_1.default.readFileSync(this.bundlePath, "utf-8");
24
+ return { htmlTemplate, jsBundle };
25
+ }
26
+ filterClaudeAPIPairs(pairs) {
27
+ return pairs.filter((pair) => {
28
+ const url = pair.request.url;
29
+ // Include both Anthropic API and Bedrock API calls
30
+ return url.includes("/v1/messages") || url.includes("bedrock-runtime.amazonaws.com");
31
+ });
32
+ }
33
+ filterShortConversations(pairs) {
34
+ return pairs.filter((pair) => {
35
+ const messages = pair.request?.body?.messages;
36
+ if (!Array.isArray(messages))
37
+ return true;
38
+ return messages.length > 2;
39
+ });
40
+ }
41
+ prepareDataForInjection(data) {
42
+ const claudeData = {
43
+ rawPairs: data.rawPairs,
44
+ timestamp: data.timestamp,
45
+ metadata: {
46
+ includeAllRequests: data.includeAllRequests || false,
47
+ },
48
+ };
49
+ // Convert to JSON with minimal whitespace
50
+ const dataJson = JSON.stringify(claudeData, null, 0);
51
+ // Base64 encode to avoid all escaping issues
52
+ return Buffer.from(dataJson, "utf-8").toString("base64");
53
+ }
54
+ escapeHtml(text) {
55
+ return text
56
+ .replace(/&/g, "&amp;")
57
+ .replace(/</g, "&lt;")
58
+ .replace(/>/g, "&gt;")
59
+ .replace(/"/g, "&quot;")
60
+ .replace(/'/g, "&#39;");
61
+ }
62
+ async generateHTML(pairs, outputFile, options = {}) {
63
+ try {
64
+ let filteredPairs = pairs;
65
+ // Remove filtering entirely - show all data
66
+ // Previously filtered to only include v1/messages pairs with messages.length >= 2
67
+ // but this was too aggressive and excluded valid data
68
+ // Load template and bundle files
69
+ const { htmlTemplate, jsBundle } = this.loadTemplateFiles();
70
+ // Prepare data for injection
71
+ const htmlData = {
72
+ rawPairs: filteredPairs,
73
+ timestamp: options.timestamp || new Date().toISOString().replace("T", " ").slice(0, -5),
74
+ includeAllRequests: options.includeAllRequests || false,
75
+ };
76
+ const dataJsonEscaped = this.prepareDataForInjection(htmlData);
77
+ // BIZARRE BUT NECESSARY: Use split() instead of replace() for bundle injection
78
+ //
79
+ // Why this weird approach? Using replace instead of split() for some reason duplicates
80
+ // the htmlTemplate itself inside the new string! Maybe a bug in Node's String.replace?
81
+ const templateParts = htmlTemplate.split("__CLAUDE_LOGGER_BUNDLE_REPLACEMENT_UNIQUE_9487__");
82
+ if (templateParts.length !== 2) {
83
+ throw new Error("Template bundle replacement marker not found or found multiple times");
84
+ }
85
+ // Reconstruct the template with the bundle injected between the split parts
86
+ let htmlContent = templateParts[0] + jsBundle + templateParts[1];
87
+ htmlContent = htmlContent
88
+ .replace("__CLAUDE_LOGGER_DATA_REPLACEMENT_UNIQUE_9487__", dataJsonEscaped)
89
+ .replace("__CLAUDE_LOGGER_TITLE_REPLACEMENT_UNIQUE_9487__", this.escapeHtml(options.title || `${filteredPairs.length} API Calls`));
90
+ // Ensure output directory exists
91
+ const outputDir = path_1.default.dirname(outputFile);
92
+ if (!fs_1.default.existsSync(outputDir)) {
93
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
94
+ }
95
+ // Write HTML file
96
+ fs_1.default.writeFileSync(outputFile, htmlContent, "utf-8");
97
+ }
98
+ catch (error) {
99
+ console.error(`Error generating HTML: ${error}`);
100
+ throw error;
101
+ }
102
+ }
103
+ async generateHTMLFromJSONL(jsonlFile, outputFile, includeAllRequests = true) {
104
+ if (!fs_1.default.existsSync(jsonlFile)) {
105
+ throw new Error(`File '${jsonlFile}' not found.`);
106
+ }
107
+ // Load all pairs from the JSONL file
108
+ const pairs = [];
109
+ const fileContent = fs_1.default.readFileSync(jsonlFile, "utf-8");
110
+ const lines = fileContent.split("\n");
111
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
112
+ const line = lines[lineNum].trim();
113
+ if (line) {
114
+ try {
115
+ const pair = JSON.parse(line);
116
+ pairs.push(pair);
117
+ }
118
+ catch (error) {
119
+ console.warn(`Warning: Skipping invalid JSON on line ${lineNum + 1}: ${line.slice(0, 100)}...`);
120
+ continue;
121
+ }
122
+ }
123
+ }
124
+ if (pairs.length === 0) {
125
+ throw new Error(`No valid data found in '${jsonlFile}'.`);
126
+ }
127
+ // Determine output file
128
+ if (!outputFile) {
129
+ outputFile = jsonlFile.replace(/\.jsonl$/, ".html");
130
+ }
131
+ await this.generateHTML(pairs, outputFile, { includeAllRequests });
132
+ return outputFile;
133
+ }
134
+ getTemplatePaths() {
135
+ return {
136
+ templatePath: this.templatePath,
137
+ bundlePath: this.bundlePath,
138
+ };
139
+ }
140
+ }
141
+ exports.HTMLGenerator = HTMLGenerator;
@@ -0,0 +1,29 @@
1
+ export interface ConversationSummary {
2
+ id: string;
3
+ title: string;
4
+ summary: string;
5
+ startTime: string;
6
+ messageCount: number;
7
+ models: string[];
8
+ }
9
+ export interface LogSummary {
10
+ logFile: string;
11
+ htmlFile: string;
12
+ generated: string;
13
+ conversations: ConversationSummary[];
14
+ }
15
+ export declare class IndexGenerator {
16
+ private traceDir;
17
+ private htmlGenerator;
18
+ private conversationProcessor;
19
+ constructor();
20
+ generateIndex(): Promise<void>;
21
+ private findLogFiles;
22
+ private processLogFile;
23
+ private extractConversations;
24
+ private summarizeConversation;
25
+ private conversationToText;
26
+ private callClaude;
27
+ private generateIndexHTML;
28
+ }
29
+ //# sourceMappingURL=index-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-generator.d.ts","sourceRoot":"","sources":["../src/index-generator.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,mBAAmB,EAAE,CAAC;CACrC;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,qBAAqB,CAA8B;;IAOrD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCpC,OAAO,CAAC,YAAY;YAON,cAAc;YA6Dd,oBAAoB;YAgBpB,qBAAqB;IA2CnC,OAAO,CAAC,kBAAkB;YAkCZ,UAAU;YAkCV,iBAAiB;CAkE/B"}
@@ -0,0 +1,271 @@
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.IndexGenerator = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const child_process_1 = require("child_process");
10
+ const html_generator_1 = require("./html-generator");
11
+ const shared_conversation_processor_1 = require("./shared-conversation-processor");
12
+ class IndexGenerator {
13
+ constructor() {
14
+ this.traceDir = ".claude-trace";
15
+ this.htmlGenerator = new html_generator_1.HTMLGenerator();
16
+ this.conversationProcessor = new shared_conversation_processor_1.SharedConversationProcessor();
17
+ }
18
+ async generateIndex() {
19
+ console.log("Generating conversation index...");
20
+ console.log(`Looking in: ${this.traceDir}/`);
21
+ if (!fs_1.default.existsSync(this.traceDir)) {
22
+ console.log(`Directory ${this.traceDir} not found`);
23
+ process.exit(1);
24
+ }
25
+ // Find all log files
26
+ const logFiles = this.findLogFiles();
27
+ console.log(`Found ${logFiles.length} log files`);
28
+ if (logFiles.length === 0) {
29
+ console.log("No log files found");
30
+ process.exit(1);
31
+ }
32
+ // Process each log file
33
+ const allSummaries = [];
34
+ for (const logFile of logFiles) {
35
+ console.log(`\nProcessing ${logFile}...`);
36
+ const summary = await this.processLogFile(logFile);
37
+ if (summary) {
38
+ allSummaries.push(summary);
39
+ }
40
+ }
41
+ // Generate index.html
42
+ await this.generateIndexHTML(allSummaries);
43
+ console.log(`\nIndex generated: ${this.traceDir}/index.html`);
44
+ }
45
+ findLogFiles() {
46
+ const files = fs_1.default.readdirSync(this.traceDir);
47
+ return files
48
+ .filter((file) => file.match(/^log-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}\.jsonl$/))
49
+ .sort((a, b) => b.localeCompare(a)); // newest first
50
+ }
51
+ async processLogFile(logFile) {
52
+ const logPath = path_1.default.join(this.traceDir, logFile);
53
+ const timestamp = logFile.match(/log-(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})\.jsonl$/)?.[1];
54
+ if (!timestamp)
55
+ return null;
56
+ const summaryFile = `summary-${timestamp}.json`;
57
+ const summaryPath = path_1.default.join(this.traceDir, summaryFile);
58
+ const htmlFile = `log-${timestamp}.html`;
59
+ const htmlPath = path_1.default.join(this.traceDir, htmlFile);
60
+ // Check if summary needs regeneration
61
+ const logStat = fs_1.default.statSync(logPath);
62
+ let needsRegeneration = !fs_1.default.existsSync(summaryPath);
63
+ if (!needsRegeneration) {
64
+ const summaryStat = fs_1.default.statSync(summaryPath);
65
+ needsRegeneration = summaryStat.mtime < logStat.mtime;
66
+ }
67
+ if (needsRegeneration) {
68
+ console.log(` Generating summary (${needsRegeneration ? "missing or outdated" : "up to date"})...`);
69
+ // Ensure HTML file exists
70
+ if (!fs_1.default.existsSync(htmlPath)) {
71
+ console.log(` Generating HTML file...`);
72
+ await this.htmlGenerator.generateHTMLFromJSONL(logPath, htmlPath);
73
+ }
74
+ // Process conversations
75
+ const conversations = await this.extractConversations(logPath);
76
+ const summaries = [];
77
+ // Summarize non-compacted conversations with more than 2 messages
78
+ const nonCompactedConversations = conversations.filter((conv) => !conv.compacted && conv.messages.length > 2);
79
+ console.log(` Found ${nonCompactedConversations.length} non-compacted conversations (>2 messages)`);
80
+ for (const conversation of nonCompactedConversations) {
81
+ console.log(` Summarizing conversation ${conversation.id}...`);
82
+ const summary = await this.summarizeConversation(conversation);
83
+ if (summary) {
84
+ summaries.push(summary);
85
+ }
86
+ }
87
+ // Save summary file
88
+ const logSummary = {
89
+ logFile,
90
+ htmlFile,
91
+ generated: new Date().toISOString(),
92
+ conversations: summaries,
93
+ };
94
+ fs_1.default.writeFileSync(summaryPath, JSON.stringify(logSummary, null, 2));
95
+ console.log(` Summary saved: ${summaryFile}`);
96
+ return logSummary;
97
+ }
98
+ else {
99
+ console.log(` Using existing summary`);
100
+ return JSON.parse(fs_1.default.readFileSync(summaryPath, "utf-8"));
101
+ }
102
+ }
103
+ async extractConversations(logPath) {
104
+ // Read and parse JSONL file
105
+ const content = fs_1.default.readFileSync(logPath, "utf-8");
106
+ const lines = content
107
+ .trim()
108
+ .split("\n")
109
+ .filter((line) => line.trim());
110
+ const rawPairs = lines.map((line) => JSON.parse(line));
111
+ // Process pairs using shared implementation
112
+ const processedPairs = this.conversationProcessor.processRawPairs(rawPairs);
113
+ // Extract conversations
114
+ return this.conversationProcessor.mergeConversations(processedPairs);
115
+ }
116
+ async summarizeConversation(conversation) {
117
+ try {
118
+ // Convert conversation to text for summarization
119
+ const conversationText = this.conversationToText(conversation);
120
+ // Prepare prompt for Claude
121
+ const prompt = `Please analyze this conversation and provide:
122
+ 1. A concise title (max 10 words)
123
+ 2. A summary in 1-3 paragraphs describing what was accomplished
124
+
125
+ Conversation:
126
+ ${conversationText}
127
+
128
+ Format your response as:
129
+ TITLE: [title]
130
+ SUMMARY: [summary]`;
131
+ // Call Claude CLI
132
+ const claudeResponse = await this.callClaude(prompt);
133
+ // Parse response
134
+ const titleMatch = claudeResponse.match(/TITLE:\s*(.+)/);
135
+ const summaryMatch = claudeResponse.match(/SUMMARY:\s*([\s\S]+)/);
136
+ if (!titleMatch || !summaryMatch) {
137
+ console.log(` Failed to parse Claude response for conversation ${conversation.id}`);
138
+ return null;
139
+ }
140
+ return {
141
+ id: conversation.id,
142
+ title: titleMatch[1].trim(),
143
+ summary: summaryMatch[1].trim(),
144
+ startTime: conversation.metadata.startTime,
145
+ messageCount: conversation.messages.length,
146
+ models: Array.from(conversation.models),
147
+ };
148
+ }
149
+ catch (error) {
150
+ console.log(` Failed to summarize conversation ${conversation.id}: ${error}`);
151
+ return null;
152
+ }
153
+ }
154
+ conversationToText(conversation) {
155
+ let text = "";
156
+ // Add system prompt (stripped)
157
+ if (conversation.system) {
158
+ const systemText = typeof conversation.system === "string"
159
+ ? conversation.system
160
+ : conversation.system.map((block) => (block.type === "text" ? block.text : "[non-text]")).join(" ");
161
+ text += `SYSTEM: ${systemText.substring(0, 500)}...\n\n`;
162
+ }
163
+ // Add messages (without tool results for brevity)
164
+ for (const message of conversation.messages) {
165
+ if (message.hide)
166
+ continue;
167
+ text += `${message.role.toUpperCase()}: `;
168
+ if (typeof message.content === "string") {
169
+ text += message.content.substring(0, 1000);
170
+ }
171
+ else if (Array.isArray(message.content)) {
172
+ const textBlocks = message.content
173
+ .filter((block) => block.type === "text")
174
+ .map((block) => block.text)
175
+ .join(" ");
176
+ text += textBlocks.substring(0, 1000);
177
+ }
178
+ text += "\n\n";
179
+ }
180
+ return text;
181
+ }
182
+ async callClaude(prompt) {
183
+ return new Promise((resolve, reject) => {
184
+ console.log(" Calling Claude CLI for summarization...");
185
+ console.log(" This will incur additional token usage");
186
+ const child = (0, child_process_1.spawn)("claude", ["-p", prompt], {
187
+ stdio: ["pipe", "pipe", "pipe"],
188
+ });
189
+ let stdout = "";
190
+ let stderr = "";
191
+ child.stdout.on("data", (data) => {
192
+ stdout += data.toString();
193
+ });
194
+ child.stderr.on("data", (data) => {
195
+ stderr += data.toString();
196
+ });
197
+ child.on("close", (code) => {
198
+ if (code === 0) {
199
+ resolve(stdout.trim());
200
+ }
201
+ else {
202
+ reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
203
+ }
204
+ });
205
+ child.on("error", (error) => {
206
+ reject(new Error(`Failed to spawn claude CLI: ${error.message}`));
207
+ });
208
+ });
209
+ }
210
+ async generateIndexHTML(summaries) {
211
+ // We'll create a simple index template for now
212
+ // Later we can enhance this with the lazy loading frontend
213
+ const indexPath = path_1.default.join(this.traceDir, "index.html");
214
+ let html = `<!DOCTYPE html>
215
+ <html lang="en">
216
+ <head>
217
+ <meta charset="UTF-8">
218
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
219
+ <title>CCDebug - Conversation Index</title>
220
+ <style>
221
+ body { font-family: system-ui, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
222
+ .header { border-bottom: 1px solid #ddd; padding-bottom: 20px; margin-bottom: 30px; }
223
+ .log-section { margin-bottom: 40px; }
224
+ .log-header { background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
225
+ .conversation { border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px; margin-bottom: 15px; }
226
+ .conversation h3 { margin-top: 0; color: #2563eb; }
227
+ .conversation-meta { color: #666; font-size: 14px; margin-bottom: 10px; }
228
+ .models { display: flex; gap: 8px; margin-top: 10px; }
229
+ .model-tag { background: #e5f3ff; color: #0066cc; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
230
+ a { color: #2563eb; text-decoration: none; }
231
+ a:hover { text-decoration: underline; }
232
+ </style>
233
+ </head>
234
+ <body>
235
+ <div class="header">
236
+ <h1>CCDebug - Conversation Index</h1>
237
+ <p>Generated: ${new Date().toISOString().replace("T", " ").slice(0, -5)}</p>
238
+ <p>Total logs: ${summaries.length}</p>
239
+ </div>
240
+ `;
241
+ for (const summary of summaries) {
242
+ html += `
243
+ <div class="log-section">
244
+ <div class="log-header">
245
+ <h2><a href="${summary.htmlFile}">${summary.logFile}</a></h2>
246
+ <p>Generated: ${summary.generated.replace("T", " ").slice(0, -5)} | Conversations: ${summary.conversations.length}</p>
247
+ </div>
248
+ `;
249
+ for (const conv of summary.conversations) {
250
+ html += `
251
+ <div class="conversation">
252
+ <h3>${conv.title}</h3>
253
+ <div class="conversation-meta">
254
+ ${conv.startTime.replace("T", " ").slice(0, -5)} | ${conv.messageCount} messages
255
+ </div>
256
+ <p>${conv.summary}</p>
257
+ <div class="models">
258
+ ${conv.models.map((model) => `<span class="model-tag">${model}</span>`).join("")}
259
+ </div>
260
+ </div>
261
+ `;
262
+ }
263
+ html += ` </div>`;
264
+ }
265
+ html += `
266
+ </body>
267
+ </html>`;
268
+ fs_1.default.writeFileSync(indexPath, html);
269
+ }
270
+ }
271
+ exports.IndexGenerator = IndexGenerator;
@@ -0,0 +1,7 @@
1
+ export { ClaudeTrafficLogger, initializeInterceptor, getLogger, InterceptorConfig } from "./interceptor";
2
+ export { HTMLGenerator } from "./html-generator";
3
+ export { RawPair, ClaudeData, HTMLGenerationData, TemplateReplacements } from "./types";
4
+ export * from "./interceptor";
5
+ export * from "./html-generator";
6
+ export * from "./types";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGxF,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.HTMLGenerator = exports.getLogger = exports.initializeInterceptor = exports.ClaudeTrafficLogger = void 0;
18
+ // Main exports for the package
19
+ var interceptor_1 = require("./interceptor");
20
+ Object.defineProperty(exports, "ClaudeTrafficLogger", { enumerable: true, get: function () { return interceptor_1.ClaudeTrafficLogger; } });
21
+ Object.defineProperty(exports, "initializeInterceptor", { enumerable: true, get: function () { return interceptor_1.initializeInterceptor; } });
22
+ Object.defineProperty(exports, "getLogger", { enumerable: true, get: function () { return interceptor_1.getLogger; } });
23
+ var html_generator_1 = require("./html-generator");
24
+ Object.defineProperty(exports, "HTMLGenerator", { enumerable: true, get: function () { return html_generator_1.HTMLGenerator; } });
25
+ // Re-export everything for convenience
26
+ __exportStar(require("./interceptor"), exports);
27
+ __exportStar(require("./html-generator"), exports);
28
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,59 @@
1
+ // CommonJS loader for interceptor
2
+ try {
3
+ // Try to load the compiled JS version first
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ // Determine the correct paths based on current location
8
+ const isRunningFromDist = __dirname.includes('dist');
9
+ const isRunningFromSrc = __dirname.includes('src');
10
+
11
+ let jsPath, tsPath;
12
+
13
+ if (isRunningFromDist) {
14
+ // Running from dist directory - interceptor.js is in the same directory
15
+ jsPath = path.join(__dirname, "interceptor.js");
16
+ tsPath = path.join(__dirname, "..", "src", "interceptor.ts");
17
+ } else if (isRunningFromSrc) {
18
+ // Running from src directory - interceptor.js is in dist directory
19
+ jsPath = path.join(__dirname, "..", "dist", "interceptor.js");
20
+ tsPath = path.join(__dirname, "interceptor.ts");
21
+ } else {
22
+ // Fallback - try common locations
23
+ jsPath = path.join(__dirname, "interceptor.js");
24
+ tsPath = path.join(__dirname, "interceptor.ts");
25
+ }
26
+
27
+ if (fs.existsSync(jsPath)) {
28
+ // Use compiled JavaScript
29
+ const interceptorModule = require(jsPath);
30
+
31
+ // Export the function for wrapper script
32
+ module.exports = {
33
+ initializeInterceptor: interceptorModule.initializeInterceptor
34
+ };
35
+
36
+ // Initialize the interceptor
37
+ interceptorModule.initializeInterceptor();
38
+ } else if (fs.existsSync(tsPath)) {
39
+ // Use TypeScript via tsx
40
+ require("tsx/cjs/api").register();
41
+ const interceptorModule = require(tsPath);
42
+
43
+ // Export the function for wrapper script
44
+ module.exports = {
45
+ initializeInterceptor: interceptorModule.initializeInterceptor
46
+ };
47
+
48
+ // Initialize the interceptor
49
+ interceptorModule.initializeInterceptor();
50
+ } else {
51
+ console.error("Could not find interceptor file");
52
+ console.error("Tried JS path:", jsPath);
53
+ console.error("Tried TS path:", tsPath);
54
+ process.exit(1);
55
+ }
56
+ } catch (error) {
57
+ console.error("Error loading interceptor:", error.message);
58
+ process.exit(1);
59
+ }
@@ -0,0 +1,46 @@
1
+ export interface InterceptorConfig {
2
+ logDirectory?: string;
3
+ logBaseName?: string;
4
+ enableRealTimeHTML?: boolean;
5
+ logLevel?: "debug" | "info" | "warn" | "error";
6
+ }
7
+ export declare class ClaudeTrafficLogger {
8
+ private traceHomeDir;
9
+ private traceLogDir;
10
+ private traceLogFile;
11
+ private ccLogDir;
12
+ private ccLogFile;
13
+ private htmlFile;
14
+ private pendingRequests;
15
+ private pairs;
16
+ private config;
17
+ private htmlGenerator;
18
+ constructor(config?: InterceptorConfig);
19
+ private isClaudeAPI;
20
+ private generateRequestId;
21
+ private redactSensitiveHeaders;
22
+ private cloneResponse;
23
+ private parseRequestBody;
24
+ private parseResponseBody;
25
+ instrumentAll(): void;
26
+ instrumentFetch(): void;
27
+ instrumentNodeHTTP(): void;
28
+ private interceptNodeRequest;
29
+ private parseNodeRequestURL;
30
+ private parseResponseBodyFromString;
31
+ private writePairToLog;
32
+ private generateHTML;
33
+ cleanup(): void;
34
+ private getSessionIdFromLog;
35
+ private copyCClogFile;
36
+ private renameTraceLogFileBySessionId;
37
+ getStats(): {
38
+ totalPairs: number;
39
+ pendingRequests: number;
40
+ logFile: string;
41
+ htmlFile: string;
42
+ };
43
+ }
44
+ export declare function initializeInterceptor(config?: InterceptorConfig): ClaudeTrafficLogger;
45
+ export declare function getLogger(): ClaudeTrafficLogger | null;
46
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,MAAM,GAAE,iBAAsB;IA6C1C,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,sBAAsB;YAgChB,aAAa;YAKb,gBAAgB;YAsBhB,iBAAiB;IAwBxB,aAAa,IAAI,IAAI;IAKrB,eAAe,IAAI,IAAI;IA0FvB,kBAAkB,IAAI,IAAI;IA8CjC,OAAO,CAAC,oBAAoB;IAuE5B,OAAO,CAAC,mBAAmB;YAab,2BAA2B;YAiB3B,cAAc;YASd,YAAY;IAcnB,OAAO,IAAI,IAAI;IA4CtB,OAAO,CAAC,mBAAmB;IA0C3B,OAAO,CAAC,aAAa;IA4DrB,OAAO,CAAC,6BAA6B;IAkB9B,QAAQ;;;;;;CAQf;AAQD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,mBAAmB,CA8BrF;AAED,wBAAgB,SAAS,IAAI,mBAAmB,GAAG,IAAI,CAEtD"}