@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
package/dist/cli.js ADDED
@@ -0,0 +1,674 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.colors = void 0;
38
+ const child_process_1 = require("child_process");
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs"));
41
+ const html_generator_1 = require("./html-generator");
42
+ const os = __importStar(require("os"));
43
+ /**
44
+ * 获取工具版本号
45
+ */
46
+ function getVersion() {
47
+ try {
48
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
49
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
50
+ return packageJson.version || "unknown";
51
+ }
52
+ catch (error) {
53
+ return "unknown";
54
+ }
55
+ }
56
+ // Colors for output
57
+ exports.colors = {
58
+ red: "\x1b[0;31m",
59
+ green: "\x1b[0;32m",
60
+ yellow: "\x1b[1;33m",
61
+ blue: "\x1b[0;34m",
62
+ reset: "\x1b[0m",
63
+ };
64
+ function log(message, color = "reset") {
65
+ console.log(`${exports.colors[color]}${message}${exports.colors.reset}`);
66
+ }
67
+ function showHelp() {
68
+ console.log(`
69
+ ${exports.colors.blue}CCDebug${exports.colors.reset}
70
+ 查看CC标准日志,跟踪CC所有API请求,允许修改并重新发起单步中的API请求,以调试CC轨迹
71
+
72
+ ${exports.colors.yellow}用法:${exports.colors.reset}
73
+ ccdebug [选项] [--run-with CLAUDE_参数...]
74
+
75
+ ${exports.colors.yellow}选项:${exports.colors.reset}
76
+ --serve, --log, -l 启动站点,查看claude code日志
77
+ --run-with 将后续所有参数传递给 Claude 进程
78
+ --claude-path 指定 Claude 二进制文件或cli.js的路径
79
+ --version, -v 显示版本信息
80
+ --help, -h 显示此帮助信息
81
+
82
+ ${exports.colors.yellow}模式:${exports.colors.reset}
83
+ ${exports.colors.green}交互式日志:${exports.colors.reset}
84
+ ccdebug 启动带流量日志的 Claude
85
+ ccdebug --run-with -p "请按要求工作" --verbose 使用特定命令运行 Claude
86
+
87
+ ${exports.colors.green}Web 服务器:${exports.colors.reset}
88
+ ccdebug --serve 启动站点,查看claude code日志
89
+ ccdebug --log 启动站点,查看claude code日志
90
+ ccdebug -l 启动站点,查看claude code日志
91
+ ccdebug --serve --port 8080 在自定义端口上启动站点
92
+ ccdebug --log --port 8080 在自定义端口上启动站点
93
+ ccdebug -l --port 8080 在自定义端口上启动站点
94
+ ccdebug --serve --project /path/to/project 为特定项目启动站点
95
+ ccdebug --log --project /path/to/project 为特定项目启动站点
96
+ ccdebug -l --project /path/to/project 为特定项目启动站点
97
+
98
+ ${exports.colors.yellow}输出:${exports.colors.reset}
99
+ cc标准日志: ${exports.colors.green}.claude-trace/cclog/*.jsonl${exports.colors.reset}
100
+ cc跟踪日志: ${exports.colors.green}.claude-trace/tracelog/*.jsonl${exports.colors.reset}
101
+
102
+ 更多信息请访问: https://github.com/myskyline_ai/ccdebug
103
+ `);
104
+ }
105
+ function resolveToJsFile(filePath) {
106
+ try {
107
+ // First, resolve any symlinks
108
+ const realPath = fs.realpathSync(filePath);
109
+ // Check if it's already a JS file
110
+ if (realPath.endsWith(".js")) {
111
+ return realPath;
112
+ }
113
+ // If it's a Node.js shebang script, check if it's actually a JS file
114
+ if (fs.existsSync(realPath)) {
115
+ const content = fs.readFileSync(realPath, "utf-8");
116
+ // Check for Node.js shebang
117
+ if (content.startsWith("#!/usr/bin/env node") ||
118
+ content.match(/^#!.*\/node$/m) ||
119
+ content.includes("require(") ||
120
+ content.includes("import ")) {
121
+ // This is likely a JS file without .js extension
122
+ return realPath;
123
+ }
124
+ }
125
+ // If not a JS file, try common JS file locations
126
+ const possibleJsPaths = [
127
+ realPath + ".js",
128
+ realPath.replace(/\/bin\//, "/lib/") + ".js",
129
+ realPath.replace(/\/\.bin\//, "/lib/bin/") + ".js",
130
+ ];
131
+ for (const jsPath of possibleJsPaths) {
132
+ if (fs.existsSync(jsPath)) {
133
+ return jsPath;
134
+ }
135
+ }
136
+ // Fall back to original path
137
+ return realPath;
138
+ }
139
+ catch (error) {
140
+ // If resolution fails, return original path
141
+ return filePath;
142
+ }
143
+ }
144
+ function getClaudeAbsolutePath(customPath) {
145
+ // If custom path is provided, use it directly
146
+ if (customPath) {
147
+ if (!fs.existsSync(customPath)) {
148
+ log(`在指定路径未找到 Claude 二进制文件: ${customPath}`, "red");
149
+ process.exit(1);
150
+ }
151
+ return resolveToJsFile(customPath);
152
+ }
153
+ // 检测操作系统
154
+ const isWindows = os.platform() === 'win32';
155
+ try {
156
+ let claudePath;
157
+ if (isWindows) {
158
+ // Windows: 使用 where 命令
159
+ try {
160
+ const whereResult = require("child_process")
161
+ .execSync("where claude", {
162
+ encoding: "utf-8",
163
+ })
164
+ .trim();
165
+ // where 命令可能返回多个结果,优先选择 Windows 批处理文件
166
+ const paths = whereResult.split('\n').map((p) => p.trim()).filter((p) => p.length > 0);
167
+ // 优先选择 .cmd 或 .bat 文件,而不是 Unix shell 脚本
168
+ claudePath = paths.find((p) => p.endsWith('.cmd') || p.endsWith('.bat')) || paths[0];
169
+ }
170
+ catch (error) {
171
+ // where 命令失败,尝试使用 PowerShell
172
+ try {
173
+ claudePath = require("child_process")
174
+ .execSync('powershell -Command "Get-Command claude | Select-Object -ExpandProperty Source"', {
175
+ encoding: "utf-8",
176
+ })
177
+ .trim();
178
+ }
179
+ catch (psError) {
180
+ throw new Error("Claude not found in PATH");
181
+ }
182
+ }
183
+ }
184
+ else {
185
+ // Linux/Mac: 使用 which 命令,但排除当前项目目录
186
+ try {
187
+ // 获取当前工作目录
188
+ const currentDir = process.cwd();
189
+ // 使用 which 命令获取所有可能的 claude 路径
190
+ const whichResult = require("child_process")
191
+ .execSync("which -a claude", {
192
+ encoding: "utf-8",
193
+ })
194
+ .trim();
195
+ // 分割所有路径
196
+ const allPaths = whichResult.split('\n').map((p) => p.trim()).filter((p) => p.length > 0);
197
+ // 找到不在当前项目目录中的路径
198
+ claudePath = allPaths.find((p) => !p.includes(currentDir));
199
+ // 如果没有找到合适的路径,使用第一个(全局)路径
200
+ if (!claudePath && allPaths.length > 0) {
201
+ claudePath = allPaths[0];
202
+ }
203
+ if (!claudePath) {
204
+ throw new Error("Claude not found in PATH");
205
+ }
206
+ }
207
+ catch (error) {
208
+ throw new Error("Claude not found in PATH");
209
+ }
210
+ }
211
+ // Handle shell aliases (e.g., "claude: aliased to /path/to/claude")
212
+ const aliasMatch = claudePath.match(/:\s*aliased to\s+(.+)$/);
213
+ if (aliasMatch && aliasMatch[1]) {
214
+ claudePath = aliasMatch[1];
215
+ }
216
+ // Check if path is a bash wrapper (Linux/Mac) or batch file (Windows)
217
+ if (fs.existsSync(claudePath)) {
218
+ const content = fs.readFileSync(claudePath, "utf-8");
219
+ if (isWindows) {
220
+ // Windows: 检查批处理文件 (.bat, .cmd) 或 PowerShell 脚本
221
+ if (claudePath.endsWith('.bat') || claudePath.endsWith('.cmd')) {
222
+ // 解析批处理文件来找到实际的 Node.js 脚本
223
+ // 匹配模式如: "%dp0%\node_modules\@anthropic-ai\claude-code\cli.js"
224
+ const matches = content.match(/["']([^"']*\.js)["']/g);
225
+ if (matches && matches.length > 0) {
226
+ // 取最后一个匹配,通常是实际的JS文件
227
+ let jsPath = matches[matches.length - 1].replace(/["']/g, '');
228
+ // 替换 %dp0% 变量为批处理文件所在目录
229
+ if (jsPath.includes('%dp0%')) {
230
+ const batchDir = path.dirname(claudePath);
231
+ jsPath = jsPath.replace(/%dp0%/g, batchDir + '\\');
232
+ }
233
+ return resolveToJsFile(jsPath);
234
+ }
235
+ }
236
+ else if (content.startsWith('#!') && content.includes('node')) {
237
+ // Node.js shebang 脚本
238
+ return resolveToJsFile(claudePath);
239
+ }
240
+ }
241
+ else {
242
+ // Linux/Mac: 检查 bash wrapper
243
+ if (content.startsWith("#!/bin/bash")) {
244
+ // Parse bash wrapper to find actual executable
245
+ const execMatch = content.match(/exec\s+"([^"]+)"/);
246
+ if (execMatch && execMatch[1]) {
247
+ const actualPath = execMatch[1];
248
+ return resolveToJsFile(actualPath);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return resolveToJsFile(claudePath);
254
+ }
255
+ catch (error) {
256
+ // First try the local installation paths
257
+ const localClaudeWrapper = path.join(os.homedir(), ".claude", "local", "claude");
258
+ const localClaudeBat = path.join(os.homedir(), ".claude", "local", "claude.bat");
259
+ const localClaudePath = path.join(os.homedir(), ".claude", "local", "node_modules", ".bin", "claude");
260
+ const localClaudeCmd = path.join(os.homedir(), ".claude", "local", "node_modules", ".bin", "claude.cmd");
261
+ // Try different local installation paths based on OS
262
+ const possiblePaths = isWindows ? [localClaudeBat, localClaudeCmd, localClaudePath] : [localClaudeWrapper, localClaudePath];
263
+ for (const tryPath of possiblePaths) {
264
+ if (fs.existsSync(tryPath)) {
265
+ const content = fs.readFileSync(tryPath, "utf-8");
266
+ if (isWindows && (tryPath.endsWith('.bat') || tryPath.endsWith('.cmd'))) {
267
+ // 解析 Windows 批处理文件
268
+ const matches = content.match(/["']([^"']*\.js)["']/g);
269
+ if (matches && matches.length > 0) {
270
+ let jsPath = matches[matches.length - 1].replace(/["']/g, '');
271
+ if (jsPath.includes('%dp0%')) {
272
+ const batchDir = path.dirname(tryPath);
273
+ jsPath = jsPath.replace(/%dp0%/g, batchDir + '\\');
274
+ }
275
+ return resolveToJsFile(jsPath);
276
+ }
277
+ }
278
+ else if (!isWindows && content.startsWith("#!/bin/bash")) {
279
+ // 解析 Linux/Mac bash wrapper
280
+ const execMatch = content.match(/exec\s+"([^"]+)"/);
281
+ if (execMatch && execMatch[1]) {
282
+ return resolveToJsFile(execMatch[1]);
283
+ }
284
+ }
285
+ return resolveToJsFile(tryPath);
286
+ }
287
+ }
288
+ log(`在 PATH 中未找到 Claude CLI`, "red");
289
+ log(`已检查本地安装位置:`, "red");
290
+ possiblePaths.forEach(p => log(` ${p}`, "red"));
291
+ log(`请先安装 Claude Code CLI`, "red");
292
+ process.exit(1);
293
+ }
294
+ }
295
+ function isNodeScript(claudePath) {
296
+ try {
297
+ return claudePath.endsWith('.js');
298
+ }
299
+ catch {
300
+ return false;
301
+ }
302
+ }
303
+ function getLoaderPath() {
304
+ // Check if we're in development mode (running from src) or production mode (running from dist)
305
+ const isDevMode = __dirname.includes('src') || !fs.existsSync(path.join(__dirname, '..', 'dist'));
306
+ let loaderPath;
307
+ if (isDevMode) {
308
+ // Development mode: use src directory
309
+ loaderPath = path.resolve(__dirname, "interceptor-loader.js");
310
+ }
311
+ else {
312
+ // Production mode: use dist directory
313
+ loaderPath = path.resolve(__dirname, "interceptor-loader.js");
314
+ }
315
+ if (!fs.existsSync(loaderPath)) {
316
+ log(`未找到拦截器加载器: ${loaderPath}`, "red");
317
+ process.exit(1);
318
+ }
319
+ return loaderPath;
320
+ }
321
+ // Scenario 1: No args -> launch node with interceptor and absolute path to claude
322
+ async function runClaudeWithInterception(claudeArgs = [], includeAllRequests = false, openInBrowser = false, customClaudePath, logBaseName) {
323
+ log("启动 Claude 并记录流量日志", "blue");
324
+ if (claudeArgs.length > 0) {
325
+ log(`Claude 参数: ${claudeArgs.join(" ")}`, "blue");
326
+ }
327
+ const claudePath = getClaudeAbsolutePath(customClaudePath);
328
+ log(`使用 Claude 二进制文件: ${claudePath}`, "blue");
329
+ let child;
330
+ if (isNodeScript(claudePath)) {
331
+ // Node.js 脚本方式:使用原有的 --require 方式
332
+ log("使用 Node.js 拦截方法", "blue");
333
+ const loaderPath = getLoaderPath();
334
+ const spawnArgs = ["--require", loaderPath, claudePath, ...claudeArgs];
335
+ child = (0, child_process_1.spawn)("node", spawnArgs, {
336
+ env: {
337
+ ...process.env,
338
+ NODE_OPTIONS: "--no-deprecation",
339
+ CLAUDE_TRACE_INCLUDE_ALL_REQUESTS: includeAllRequests ? "true" : "false",
340
+ CLAUDE_TRACE_OPEN_BROWSER: openInBrowser ? "true" : "false",
341
+ ...(logBaseName ? { CLAUDE_TRACE_LOG_NAME: logBaseName } : {}),
342
+ },
343
+ stdio: "inherit",
344
+ cwd: process.cwd(),
345
+ });
346
+ }
347
+ else {
348
+ // ===== 方式 2: 二进制文件 - 无法拦截(给出提示)=====
349
+ console.log("");
350
+ log("⚠️ 警告: 检测到原生二进制文件", "yellow");
351
+ log("CCDebug 无法拦截来自 Claude Code 原生二进制版本的 API 请求,调试功能将无法工作", "yellow");
352
+ log("要使用 CCDebug 的完整调试功能,请改用 NPM 版本的 Claude Code。", "yellow");
353
+ console.log("");
354
+ log("正在启动 Claude(不进行 API 拦截)...", "blue");
355
+ console.log("");
356
+ // 给用户一点时间阅读提示信息
357
+ await new Promise(resolve => setTimeout(resolve, 500));
358
+ // 直接启动 Claude 二进制文件,不使用代理
359
+ child = (0, child_process_1.spawn)(claudePath, claudeArgs, {
360
+ env: {
361
+ ...process.env,
362
+ },
363
+ stdio: "inherit",
364
+ cwd: process.cwd(),
365
+ });
366
+ }
367
+ // Node.js 模式显示成功消息
368
+ if (isNodeScript(claudePath)) {
369
+ log("流量日志记录器启动成功", "green");
370
+ console.log("");
371
+ }
372
+ // Handle child process events
373
+ child.on("error", (error) => {
374
+ log(`启动 Claude 时出错: ${error.message}`, "red");
375
+ process.exit(1);
376
+ });
377
+ child.on("exit", (code, signal) => {
378
+ if (signal) {
379
+ log(`\nClaude 被信号终止: ${signal}`, "yellow");
380
+ }
381
+ else if (code !== 0 && code !== null) {
382
+ log(`\nClaude 退出,退出码: ${code}`, "yellow");
383
+ }
384
+ else {
385
+ log("\nClaude 会话已完成", "green");
386
+ }
387
+ });
388
+ // Handle our own signals
389
+ const handleSignal = (signal) => {
390
+ log(`\n收到 ${signal} 信号,正在关闭...`, "yellow");
391
+ if (child.pid) {
392
+ child.kill(signal);
393
+ }
394
+ };
395
+ process.on("SIGINT", () => handleSignal("SIGINT"));
396
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
397
+ // Wait for child process to complete
398
+ try {
399
+ await new Promise((resolve, reject) => {
400
+ child.on("exit", () => resolve());
401
+ child.on("error", reject);
402
+ });
403
+ }
404
+ catch (error) {
405
+ const err = error;
406
+ log(`意外错误: ${err.message}`, "red");
407
+ process.exit(1);
408
+ }
409
+ }
410
+ // Scenario 2: --extract-token -> launch node with token interceptor and absolute path to claude
411
+ async function extractToken(customClaudePath) {
412
+ const claudePath = getClaudeAbsolutePath(customClaudePath);
413
+ // Log to stderr so it doesn't interfere with token output
414
+ console.error(`使用 Claude 二进制文件: ${claudePath}`);
415
+ // Create .claude-trace directory if it doesn't exist
416
+ const ccdebugDir = path.join(process.cwd(), ".claude-trace");
417
+ if (!fs.existsSync(ccdebugDir)) {
418
+ fs.mkdirSync(ccdebugDir, { recursive: true });
419
+ }
420
+ // Token file location
421
+ const tokenFile = path.join(ccdebugDir, "token.txt");
422
+ // Use the token extractor directly without copying
423
+ const tokenExtractorPath = path.join(__dirname, "token-extractor.js");
424
+ if (!fs.existsSync(tokenExtractorPath)) {
425
+ log(`未找到令牌提取器: ${tokenExtractorPath}`, "red");
426
+ process.exit(1);
427
+ }
428
+ const cleanup = () => {
429
+ try {
430
+ if (fs.existsSync(tokenFile))
431
+ fs.unlinkSync(tokenFile);
432
+ }
433
+ catch (e) {
434
+ // Ignore cleanup errors
435
+ }
436
+ };
437
+ // Launch node with token interceptor and absolute path to claude
438
+ const { ANTHROPIC_API_KEY, ...envWithoutApiKey } = process.env;
439
+ const child = (0, child_process_1.spawn)("node", ["--require", tokenExtractorPath, claudePath, "-p", "hello"], {
440
+ env: {
441
+ ...envWithoutApiKey,
442
+ NODE_TLS_REJECT_UNAUTHORIZED: "0",
443
+ CLAUDE_TRACE_TOKEN_FILE: tokenFile,
444
+ },
445
+ stdio: "inherit", // Suppress all output from Claude
446
+ cwd: process.cwd(),
447
+ });
448
+ // Set a timeout to avoid hanging
449
+ const timeout = setTimeout(() => {
450
+ child.kill();
451
+ cleanup();
452
+ console.error("超时: 30 秒内未找到令牌");
453
+ process.exit(1);
454
+ }, 30000);
455
+ // Handle child process events
456
+ child.on("error", (error) => {
457
+ clearTimeout(timeout);
458
+ cleanup();
459
+ console.error(`启动 Claude 时出错: ${error.message}`);
460
+ process.exit(1);
461
+ });
462
+ child.on("exit", () => {
463
+ clearTimeout(timeout);
464
+ try {
465
+ if (fs.existsSync(tokenFile)) {
466
+ const token = fs.readFileSync(tokenFile, "utf-8").trim();
467
+ cleanup();
468
+ if (token) {
469
+ // Only output the token, nothing else
470
+ console.log(token);
471
+ process.exit(0);
472
+ }
473
+ }
474
+ }
475
+ catch (e) {
476
+ // File doesn't exist or read error
477
+ }
478
+ cleanup();
479
+ console.error("未找到授权令牌");
480
+ process.exit(1);
481
+ });
482
+ // Check for token file periodically
483
+ const checkToken = setInterval(() => {
484
+ try {
485
+ if (fs.existsSync(tokenFile)) {
486
+ const token = fs.readFileSync(tokenFile, "utf-8").trim();
487
+ if (token) {
488
+ clearTimeout(timeout);
489
+ clearInterval(checkToken);
490
+ child.kill();
491
+ cleanup();
492
+ // Only output the token, nothing else
493
+ console.log(token);
494
+ process.exit(0);
495
+ }
496
+ }
497
+ }
498
+ catch (e) {
499
+ // Ignore read errors, keep trying
500
+ }
501
+ }, 500);
502
+ }
503
+ // Scenario 3: --generate-html input.jsonl output.html
504
+ async function generateHTMLFromCLI(inputFile, outputFile, includeAllRequests = false, openInBrowser = false) {
505
+ try {
506
+ const htmlGenerator = new html_generator_1.HTMLGenerator();
507
+ const finalOutputFile = await htmlGenerator.generateHTMLFromJSONL(inputFile, outputFile, includeAllRequests);
508
+ if (openInBrowser) {
509
+ (0, child_process_1.spawn)("open", [finalOutputFile], { detached: true, stdio: "ignore" }).unref();
510
+ log(`正在浏览器中打开 ${finalOutputFile}`, "green");
511
+ }
512
+ process.exit(0);
513
+ }
514
+ catch (error) {
515
+ const err = error;
516
+ log(`错误: ${err.message}`, "red");
517
+ process.exit(1);
518
+ }
519
+ }
520
+ // Scenario 5: --serve, --log, -l
521
+ async function startWebServer(port, projectDir) {
522
+ try {
523
+ // 使用 require 导入 web server 模块
524
+ const webServerPath = path.resolve(__dirname, "../web/server/index.js");
525
+ const webServerModule = require(webServerPath);
526
+ const startServer = webServerModule.startWebServer;
527
+ const serverPort = port || 3001;
528
+ const serverProjectDir = projectDir || process.cwd();
529
+ log("CCDebug Web 服务器", "blue");
530
+ log(`正在端口 ${serverPort} 上启动 Web 时间线服务器`, "yellow");
531
+ log(`项目目录: ${serverProjectDir}`, "blue");
532
+ console.log("");
533
+ await startServer({
534
+ projectDir: path.resolve(serverProjectDir),
535
+ port: serverPort,
536
+ staticDir: path.resolve(__dirname, "../web/dist")
537
+ });
538
+ log(`Web 服务器启动成功!`, "green");
539
+ log(`在浏览器中打开 http://localhost:${serverPort}`, "green");
540
+ // 保持进程运行
541
+ process.on('SIGINT', () => {
542
+ log('\n正在关闭 Web 服务器...', "yellow");
543
+ process.exit(0);
544
+ });
545
+ }
546
+ catch (error) {
547
+ const err = error;
548
+ log(`启动 Web 服务器时出错: ${err.message}`, "red");
549
+ process.exit(1);
550
+ }
551
+ }
552
+ // Scenario 4: --index
553
+ async function generateIndex() {
554
+ try {
555
+ const { IndexGenerator } = await Promise.resolve().then(() => __importStar(require("./index-generator")));
556
+ const indexGenerator = new IndexGenerator();
557
+ await indexGenerator.generateIndex();
558
+ process.exit(0);
559
+ }
560
+ catch (error) {
561
+ const err = error;
562
+ log(`错误: ${err.message}`, "red");
563
+ process.exit(1);
564
+ }
565
+ }
566
+ // Main entry point
567
+ async function main() {
568
+ const args = process.argv.slice(2);
569
+ // Split arguments at --run-with flag
570
+ const argIndex = args.indexOf("--run-with");
571
+ let claudeTraceArgs;
572
+ let claudeArgs;
573
+ if (argIndex !== -1) {
574
+ claudeTraceArgs = args.slice(0, argIndex);
575
+ claudeArgs = args.slice(argIndex + 1);
576
+ }
577
+ else {
578
+ claudeTraceArgs = args;
579
+ claudeArgs = [];
580
+ }
581
+ // Check for version flags
582
+ if (claudeTraceArgs.includes("--version") || claudeTraceArgs.includes("-v")) {
583
+ console.log(`CCDebug version ${getVersion()}`);
584
+ process.exit(0);
585
+ }
586
+ // Check for help flags
587
+ if (claudeTraceArgs.includes("--help") || claudeTraceArgs.includes("-h")) {
588
+ showHelp();
589
+ process.exit(0);
590
+ }
591
+ // Check for include all requests flag
592
+ const includeAllRequests = claudeTraceArgs.includes("--include-all-requests");
593
+ // Check for no-open flag (inverted logic - open by default)
594
+ const openInBrowser = !claudeTraceArgs.includes("--no-open");
595
+ // Check for custom Claude path
596
+ let customClaudePath;
597
+ const claudePathIndex = claudeTraceArgs.indexOf("--claude-path");
598
+ if (claudePathIndex !== -1 && claudeTraceArgs[claudePathIndex + 1]) {
599
+ customClaudePath = claudeTraceArgs[claudePathIndex + 1];
600
+ }
601
+ // Check for custom log base name
602
+ let logBaseName;
603
+ const logIndex = claudeTraceArgs.indexOf("--log");
604
+ if (logIndex !== -1 && claudeTraceArgs[logIndex + 1]) {
605
+ logBaseName = claudeTraceArgs[logIndex + 1];
606
+ }
607
+ // Check for serve command options
608
+ let servePort;
609
+ let serveProjectDir;
610
+ const portIndex = claudeTraceArgs.indexOf("--port");
611
+ if (portIndex !== -1 && claudeTraceArgs[portIndex + 1]) {
612
+ servePort = parseInt(claudeTraceArgs[portIndex + 1], 10);
613
+ if (isNaN(servePort)) {
614
+ log(`无效的端口号: ${claudeTraceArgs[portIndex + 1]}`, "red");
615
+ process.exit(1);
616
+ }
617
+ }
618
+ const projectIndex = claudeTraceArgs.indexOf("--project");
619
+ if (projectIndex !== -1 && claudeTraceArgs[projectIndex + 1]) {
620
+ serveProjectDir = claudeTraceArgs[projectIndex + 1];
621
+ }
622
+ // Scenario 2: --extract-token
623
+ if (claudeTraceArgs.includes("--extract-token")) {
624
+ await extractToken(customClaudePath);
625
+ return;
626
+ }
627
+ // Scenario 3: --generate-html input.jsonl output.html
628
+ if (claudeTraceArgs.includes("--generate-html")) {
629
+ const flagIndex = claudeTraceArgs.indexOf("--generate-html");
630
+ const inputFile = claudeTraceArgs[flagIndex + 1];
631
+ // Find is next argument that's not a flag as the output file
632
+ let outputFile;
633
+ for (let i = flagIndex + 2; i < claudeTraceArgs.length; i++) {
634
+ const arg = claudeTraceArgs[i];
635
+ if (!arg.startsWith("--")) {
636
+ outputFile = arg;
637
+ break;
638
+ }
639
+ }
640
+ if (!inputFile) {
641
+ log(`--generate-html 缺少输入文件`, "red");
642
+ log(`用法: ccdebug --generate-html input.jsonl [output.html]`, "yellow");
643
+ process.exit(1);
644
+ }
645
+ await generateHTMLFromCLI(inputFile, outputFile, includeAllRequests, openInBrowser);
646
+ return;
647
+ }
648
+ // Scenario 4: --index
649
+ if (claudeTraceArgs.includes("--index")) {
650
+ await generateIndex();
651
+ return;
652
+ }
653
+ // Scenario 5: --serve, --log, -l
654
+ const hasServeFlag = claudeTraceArgs.includes("--serve");
655
+ const hasLogFlag = claudeTraceArgs.includes("--log") || claudeTraceArgs.includes("-l");
656
+ // Check if --log is used with a value (not as a flag)
657
+ const logFlagIndex = claudeTraceArgs.indexOf("--log");
658
+ const isLogWithValue = logFlagIndex !== -1 &&
659
+ logFlagIndex + 1 < claudeTraceArgs.length &&
660
+ !claudeTraceArgs[logFlagIndex + 1].startsWith("--");
661
+ // Only treat --log as a serve flag if it's not used with a value
662
+ const hasLogFlagAsOption = hasLogFlag && !isLogWithValue;
663
+ if (hasServeFlag || hasLogFlagAsOption) {
664
+ await startWebServer(servePort, serveProjectDir);
665
+ return;
666
+ }
667
+ // Scenario 1: No args (or claude with args) -> launch claude with interception
668
+ await runClaudeWithInterception(claudeArgs, includeAllRequests, openInBrowser, customClaudePath, logBaseName);
669
+ }
670
+ main().catch((error) => {
671
+ const err = error;
672
+ log(`意外错误: ${err.message}`, "red");
673
+ process.exit(1);
674
+ });