@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.
- package/LICENSE +201 -0
- package/README.md +129 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +674 -0
- package/dist/html-generator.d.ts +24 -0
- package/dist/html-generator.d.ts.map +1 -0
- package/dist/html-generator.js +141 -0
- package/dist/index-generator.d.ts +29 -0
- package/dist/index-generator.d.ts.map +1 -0
- package/dist/index-generator.js +271 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/interceptor-loader.js +59 -0
- package/dist/interceptor.d.ts +46 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +555 -0
- package/dist/log-file-manager.d.ts +15 -0
- package/dist/log-file-manager.d.ts.map +1 -0
- package/dist/log-file-manager.js +41 -0
- package/dist/shared-conversation-processor.d.ts +114 -0
- package/dist/shared-conversation-processor.d.ts.map +1 -0
- package/dist/shared-conversation-processor.js +663 -0
- package/dist/token-extractor.js +28 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/frontend/dist/index.global.js +1522 -0
- package/frontend/dist/styles.css +985 -0
- package/frontend/template.html +19 -0
- package/package.json +83 -0
- package/web/debug.html +14 -0
- package/web/dist/assets/index-BIP9r3RA.js +48 -0
- package/web/dist/assets/index-BIP9r3RA.js.map +1 -0
- package/web/dist/assets/index-De3gn-G-.css +1 -0
- package/web/dist/favicon.svg +4 -0
- package/web/dist/index.html +15 -0
- package/web/index.html +14 -0
- package/web/package.json +47 -0
- package/web/server/conversation-parser.d.ts +47 -0
- package/web/server/conversation-parser.d.ts.map +1 -0
- package/web/server/conversation-parser.js +564 -0
- package/web/server/conversation-parser.js.map +1 -0
- package/web/server/index.d.ts +16 -0
- package/web/server/index.d.ts.map +1 -0
- package/web/server/index.js +60 -0
- package/web/server/index.js.map +1 -0
- package/web/server/log-file-manager.d.ts +98 -0
- package/web/server/log-file-manager.d.ts.map +1 -0
- package/web/server/log-file-manager.js +512 -0
- package/web/server/log-file-manager.js.map +1 -0
- package/web/server/src/types/index.d.ts +68 -0
- package/web/server/src/types/index.d.ts.map +1 -0
- package/web/server/src/types/index.js +3 -0
- package/web/server/src/types/index.js.map +1 -0
- package/web/server/test-path.js +48 -0
- package/web/server/web-server.d.ts +41 -0
- package/web/server/web-server.d.ts.map +1 -0
- package/web/server/web-server.js +807 -0
- 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
|
+
});
|