@myskyline_ai/ccdebug 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +67 -57
- package/README.md +34 -20
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +149 -158
- package/dist/interceptor.d.ts +0 -3
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +0 -136
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -1,131 +1,141 @@
|
|
|
1
|
+
|
|
1
2
|
# CCDebug - Claude Code Debugging Tool
|
|
2
3
|
|
|
3
|
-
CCDebug is a debugging tool for Claude Code. It records and visualizes Claude Code execution traces, and also supports “edit & replay” for a single LLM request
|
|
4
|
+
CCDebug is a debugging tool for Claude Code. It records and visualizes Claude Code execution traces, and also supports “edit & replay” for a single LLM request to help you quickly pinpoint deviations caused by prompts/context/tool calls. (This project is a derivative work based on [lemmy/claude-trace](https://github.com/badlogic/lemmy/tree/main/apps/claude-trace).)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[>>中文 README](./README.md)
|
|
6
7
|
|
|
7
8
|
## ✨ Key Features
|
|
8
9
|
|
|
9
|
-
###
|
|
10
|
+
### **1. Timeline trace view**
|
|
11
|
+
- **Show by Claude Code execution steps**: distinguish different log types such as user input, LLM replies, and tool calls
|
|
12
|
+
- **Step details supported**: quickly inspect the raw Claude Code log content for any step
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
### **2. Quick log switching**
|
|
17
|
+
- **One-click jump to SubAgent logs**: jump to sub-agent logs to inspect the sub-agent execution trace
|
|
18
|
+
- **Quickly switch projects and sessions**: switch between different projects and sessions under `~/.claude/projects` without running the tool multiple times
|
|
19
|
+
|
|
20
|
+

|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
### **3. Fast search & pinpointing**
|
|
23
|
+
- **Global keyword search**: quickly locate logs containing a given keyword (searches all log files under the current session)
|
|
24
|
+
- **Step overview filtering**: the overview shows the whole execution; filter timeline nodes by type
|
|
25
|
+
- **Generate share links**: generate a share link for the current session for collaborative analysis
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
- **Filter by step type**: filter timeline nodes by type (user message, assistant reply, tool call, etc.).
|
|
15
|
-
- **Combined tool call + result**: tool input and its output are shown together for easier inspection.
|
|
16
|
-
- **Session selector & sub-agent labels**: choose a main session log; sub-agent logs are shown using agent name/description.
|
|
17
|
-
- **Project switching**: switch projects under `~/.claude/projects` in the Web UI to avoid running multiple servers.
|
|
27
|
+

|
|
18
28
|
|
|
19
|
-
###
|
|
29
|
+
### **4. Quick latency analysis**
|
|
30
|
+
- **Show step start time**: each log node displays the start time of the step
|
|
31
|
+
- **Show tool-call duration**: for tool calls and SubAgent steps, nodes display the duration
|
|
32
|
+
- **Measure time between steps**: mark two step nodes to automatically calculate and display the elapsed time between them
|
|
20
33
|
|
|
21
|
-

|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
### **5. Step-level debugging for Claude Code**
|
|
37
|
+
> Note: the debugging feature only supports Claude Code installed via the NPM script form, and does not support the native binary version.
|
|
24
38
|
|
|
25
|
-
-
|
|
26
|
-
- **
|
|
39
|
+

|
|
40
|
+
- **Track LLM requests**: record all Claude Code request logs to the LLM in detail
|
|
41
|
+
- **Resend LLM requests**: edit the LLM request payload and resend it, making it easy to repeatedly validate whether the response meets expectations
|
|
27
42
|
|
|
28
43
|
## 🚀 Quick Start
|
|
29
44
|
|
|
30
45
|
### Install
|
|
31
46
|
|
|
32
47
|
```bash
|
|
33
|
-
# Recommended: install from npm
|
|
48
|
+
# Recommended: install globally from npm
|
|
34
49
|
npm install -g @myskyline_ai/ccdebug
|
|
35
50
|
|
|
36
|
-
# Or install
|
|
51
|
+
# Or: install a local/released tgz artifact
|
|
37
52
|
# npm install -g /path/to/@myskyline_ai-ccdebug-x.y.z.tgz
|
|
38
53
|
```
|
|
39
54
|
|
|
40
55
|
### Basic usage
|
|
41
56
|
|
|
42
|
-
#### 1
|
|
57
|
+
#### 1. Launch Claude and record interactions
|
|
43
58
|
|
|
44
59
|
```bash
|
|
45
|
-
# Basic usage - start Claude and record
|
|
60
|
+
# Basic usage - start Claude and record automatically
|
|
46
61
|
ccdebug
|
|
47
62
|
|
|
48
|
-
# Include all requests (not only
|
|
63
|
+
# Include all requests (not only conversations)
|
|
49
64
|
ccdebug --include-all-requests
|
|
50
65
|
|
|
51
|
-
# Pass
|
|
52
|
-
ccdebug --run-with -p "
|
|
66
|
+
# Pass subsequent args to the Claude process (example)
|
|
67
|
+
ccdebug --run-with -p "Please work as requested" --verbose
|
|
53
68
|
```
|
|
54
69
|
|
|
55
|
-
#### 2
|
|
70
|
+
#### 2. Start the Web site to view the timeline trace
|
|
56
71
|
|
|
57
72
|
```bash
|
|
58
|
-
# Start the Web server (default port: 3001
|
|
59
|
-
ccdebug
|
|
73
|
+
# Start the Web server for the timeline (default port: 3001; default project dir: current directory)
|
|
74
|
+
ccdebug -l
|
|
60
75
|
|
|
61
|
-
#
|
|
62
|
-
ccdebug
|
|
76
|
+
# Start on a custom port
|
|
77
|
+
ccdebug -l --port 3001
|
|
63
78
|
|
|
64
|
-
#
|
|
65
|
-
ccdebug
|
|
79
|
+
# Start with a specified project directory
|
|
80
|
+
ccdebug -l --project /path/to/your/cc_workdir
|
|
66
81
|
```
|
|
67
82
|
|
|
68
83
|
### Log output directories
|
|
69
84
|
|
|
70
85
|
- **Claude Code standard logs (for timeline)**: `.claude-trace/cclog/*.jsonl` (includes main logs and `agent-*.jsonl` sub-agent logs)
|
|
71
|
-
- **Claude API tracing logs (for LLM debugging)**: `.claude-trace/tracelog/*.jsonl`
|
|
72
|
-
- **Saved LLM
|
|
86
|
+
- **Claude Code API tracing logs (for LLM request debugging)**: `.claude-trace/tracelog/*.jsonl`
|
|
87
|
+
- **Saved LLM requests (for override/replay)**: `.claude-trace/tracelog/llm_requests/*.json`
|
|
73
88
|
|
|
74
89
|
Notes:
|
|
75
90
|
|
|
76
|
-
- After
|
|
77
|
-
- If you are using the **native Claude Code binary** (not the
|
|
91
|
+
- After you run `ccdebug` to start a Claude session, CCDebug automatically copies the corresponding Claude Code standard logs into `.claude-trace/cclog/`, and renames the API tracing log to `{sessionId}.jsonl`.
|
|
92
|
+
- If you are using the **native Claude Code binary version** (not the NPM script form), CCDebug cannot intercept API requests, and LLM request debugging will be unavailable (you can still view existing Claude Code standard logs via the Web site).
|
|
78
93
|
|
|
79
94
|
## 📋 CLI Options
|
|
80
95
|
|
|
81
96
|
| Option | Description |
|
|
82
97
|
|------|------|
|
|
83
|
-
| `--
|
|
84
|
-
| `--log, -l` | Start the Web timeline server (`--log` without a value behaves like `--serve`; prefer `-l` to avoid confusion with `--log <name>`) |
|
|
98
|
+
| `--log, -l` | Start the Web timeline server (when `--log` is used without a value, it is equivalent to `--serve`; prefer `-l` to avoid confusion with `--log <name>`) |
|
|
85
99
|
| `--port <number>` | Web server port (default: 3001) |
|
|
86
|
-
| `--project <path>` | Project directory |
|
|
100
|
+
| `--project <path>` | Project directory path |
|
|
87
101
|
| `--run-with <args>` | Pass subsequent args to the Claude process |
|
|
88
|
-
| `--include-all-requests` |
|
|
89
|
-
| `--
|
|
90
|
-
| `--
|
|
91
|
-
| `--
|
|
92
|
-
| `--generate-html <input.jsonl> [output.html]` | Generate an HTML report from a JSONL file |
|
|
93
|
-
| `--index` | Generate conversation summaries & an index under `.claude-trace/` (will call Claude and incur token usage) |
|
|
94
|
-
| `--extract-token` | Extract OAuth token and exit |
|
|
95
|
-
| `--version, -v` | Print version |
|
|
102
|
+
| `--include-all-requests` | Include all fetch requests, not only conversations |
|
|
103
|
+
| `--claude-path <path>` | Custom path to the Claude binary |
|
|
104
|
+
| `--index` | Generate conversation summaries and an index for the `.claude-trace/` directory (will call Claude and incur extra token usage) |
|
|
105
|
+
| `--version, -v` | Show version information |
|
|
96
106
|
| `--help, -h` | Show help |
|
|
97
107
|
|
|
98
108
|
## 🏗️ Architecture
|
|
99
109
|
|
|
100
110
|
### Core components
|
|
101
111
|
|
|
102
|
-
- **HTTP/API interceptor**:
|
|
103
|
-
- **
|
|
104
|
-
- **Web server**: Express provides APIs for file listing, session management, project switching, and LLM request read/save/replay
|
|
105
|
-
- **Frontend**: Vue 3 + Vite + Pinia + Arco Design
|
|
112
|
+
- **HTTP/API interceptor**: based on Node.js HTTP/HTTPS + fetch interception, recording requests and responses to Anthropic/Bedrock
|
|
113
|
+
- **Claude Code standard log consolidation**: on session exit, automatically copies Claude Code standard logs (main and sub-agent logs) into `.claude-trace/cclog/`
|
|
114
|
+
- **Web server**: Express.js provides APIs for file listing, session management, project switching, and LLM request read/save/replay
|
|
115
|
+
- **Frontend UI**: Vue 3 + Vite + Pinia + Arco Design
|
|
106
116
|
|
|
107
117
|
### Data flow
|
|
108
118
|
|
|
109
119
|
```
|
|
110
|
-
HTTP request/response → interceptor → raw JSONL →
|
|
120
|
+
HTTP request/response → interceptor → raw data (JSONL) → data processor → structured data → Web UI
|
|
111
121
|
```
|
|
112
122
|
|
|
113
123
|
## 📁 Project Structure
|
|
114
124
|
|
|
115
125
|
```
|
|
116
126
|
ccdebug/
|
|
117
|
-
├── src/ # CLI
|
|
127
|
+
├── src/ # CLI and interceptors
|
|
118
128
|
│ ├── cli.ts # CLI entry
|
|
119
129
|
│ ├── interceptor.ts # API interception and tracelog recording
|
|
120
130
|
│ ├── html-generator.ts # HTML report generator (based on frontend)
|
|
121
|
-
│ └── index-generator.ts # conversation summaries and index
|
|
131
|
+
│ └── index-generator.ts # conversation summaries and index generation
|
|
122
132
|
├── web/ # Web timeline site (Vite + Vue 3)
|
|
123
133
|
│ ├── src/ # frontend source
|
|
124
134
|
│ ├── dist/ # build output
|
|
125
|
-
│ └── server/ # Express backend (
|
|
135
|
+
│ └── server/ # Express backend (started by the CLI via require)
|
|
126
136
|
├── frontend/ # standalone HTML report frontend (bundle injected into HTML)
|
|
127
137
|
├── scripts/ # packaging scripts
|
|
128
|
-
└── docs/ #
|
|
138
|
+
└── docs/ # documentation and design notes
|
|
129
139
|
```
|
|
130
140
|
|
|
131
141
|
## 🔧 Development
|
|
@@ -138,7 +148,7 @@ ccdebug/
|
|
|
138
148
|
### Local development
|
|
139
149
|
|
|
140
150
|
```bash
|
|
141
|
-
# Clone
|
|
151
|
+
# Clone the repo
|
|
142
152
|
git clone https://github.com/ThinkingBeing/ccdebug.git
|
|
143
153
|
cd ccdebug
|
|
144
154
|
|
|
@@ -148,14 +158,14 @@ npm install
|
|
|
148
158
|
# Build
|
|
149
159
|
npm run build
|
|
150
160
|
|
|
151
|
-
#
|
|
161
|
+
# Development mode (watch core code + frontend)
|
|
152
162
|
npm run dev
|
|
153
163
|
|
|
154
|
-
# Run CLI via tsx (for development
|
|
164
|
+
# Run the CLI via tsx (for development debugging)
|
|
155
165
|
npx tsx src/cli.ts --help
|
|
156
|
-
npx tsx src/cli.ts
|
|
166
|
+
npx tsx src/cli.ts -l --port 3001 --project /path/to/your/cc_workdir
|
|
157
167
|
|
|
158
|
-
# Package (artifacts will be
|
|
168
|
+
# Package after validation (artifacts will be under release/)
|
|
159
169
|
npm run package
|
|
160
170
|
```
|
|
161
171
|
|
package/README.md
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
# CCDebug - Claude Code 调试工具
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
CCDebug 是一个针对 Claude Code 的调试工具,用于记录并可视化 CC 运行轨迹,同时支持对单个 LLM
|
|
4
|
+
CCDebug 是一个针对 Claude Code 的调试工具,用于记录并可视化 CC 运行轨迹,同时支持对单个 LLM 请求进行“修改-重放”,帮助你快速定位提示词/上下文/工具调用导致的偏差。(本项目基于 [lemmy/claude-trace](https://github.com/badlogic/lemmy/tree/main/apps/claude-trace) 二次开发)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
[>>English README](./README.en.md)
|
|
7
7
|
|
|
8
8
|
## ✨ 主要功能
|
|
9
9
|
|
|
10
|
-
###
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
### **1、时间线展示轨迹**
|
|
11
|
+
- **按CC执行步骤显示**: 可以区分不同类型日志,用户输入、LLM回复、工具调用等
|
|
12
|
+
- **支持步骤详情**: 可以快速查看某一步的原始CC日志内容
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
### **2、快速切换日志**
|
|
17
|
+
- **一键跳转SubAgent日志**: 一键跳转到子代理日志,方便查看子代理的运行轨迹
|
|
18
|
+
- **快速切换项目和会话**: 直接切换 `~/.claude/projects` 下的不同项目和会话,无需启动多次工具
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
### **3、快速搜索定位**
|
|
23
|
+
- **全局搜索关键字**: 可以快速定位到包含指定关键字的日志(搜索当前会话下所有日志文件)
|
|
24
|
+
- **步骤概览过滤**: 步骤概览显示执行全貌,可按不同类型的时间线节点过滤
|
|
25
|
+
- **生成分享链接**: 可以生成当前会话的分享链接,方便多人协作分析
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
### **4、快速分析耗时**
|
|
30
|
+
- **显示步骤开始时间**: 每个日志节点显示当前步骤的开始时间
|
|
31
|
+
- **显示工具调用耗时**: 对于工具调用、SubAgent执行的步骤,节点将显示调用耗时
|
|
32
|
+
- **统计步骤间耗时**: 用户可以标记2个步骤节点,共计自动计算并显示2个节点间耗时
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
### **5、对CC步骤单点调试**
|
|
37
|
+
> 注意:调试功能仅支持NPM脚本形式安装的 Claude Code,不支持原生二进制版本。
|
|
17
38
|
|
|
18
|
-
### 🛠️ 对CC步骤单点调试
|
|
19
39
|

|
|
20
|
-

|
|
21
40
|
- **追踪LLM请求**: 详细记录CC对LLM的所有请求日志
|
|
22
41
|
- **重新发送LLM请求**: 支持修改LLM请求数据,重新发送请求,方便反复验证LLM的响应是否符合预期
|
|
23
42
|
|
|
@@ -52,13 +71,13 @@ ccdebug --run-with -p "请按要求工作" --verbose
|
|
|
52
71
|
|
|
53
72
|
```bash
|
|
54
73
|
# 启动 Web 服务器查看时间线(默认端口 3001,默认项目目录为当前目录)
|
|
55
|
-
ccdebug
|
|
74
|
+
ccdebug -l
|
|
56
75
|
|
|
57
76
|
# 在自定义端口启动
|
|
58
|
-
ccdebug
|
|
77
|
+
ccdebug -l --port 3001
|
|
59
78
|
|
|
60
79
|
# 指定项目目录启动
|
|
61
|
-
ccdebug
|
|
80
|
+
ccdebug -l --project /path/to/your/cc_workdir
|
|
62
81
|
```
|
|
63
82
|
|
|
64
83
|
### 日志输出目录
|
|
@@ -76,18 +95,13 @@ ccdebug --serve --project /path/to/your/cc_workdir
|
|
|
76
95
|
|
|
77
96
|
| 选项 | 描述 |
|
|
78
97
|
|------|------|
|
|
79
|
-
| `--serve` | 启动 Web 时间线服务器 |
|
|
80
98
|
| `--log, -l` | 启动 Web 时间线服务器(当 `--log` 不带参数时等同于 `--serve`;建议用 `-l` 避免与 `--log <name>` 混淆) |
|
|
81
99
|
| `--port <number>` | 指定 Web 服务器端口(默认 3001) |
|
|
82
100
|
| `--project <path>` | 指定项目目录路径 |
|
|
83
101
|
| `--run-with <args>` | 将后续参数传递给 Claude 进程 |
|
|
84
102
|
| `--include-all-requests` | 包含所有 fetch 请求,而不仅仅是对话 |
|
|
85
|
-
| `--no-open` | 不在浏览器中自动打开生成的 HTML(当前仅对 `--generate-html` 生效) |
|
|
86
103
|
| `--claude-path <path>` | 指定 Claude 二进制文件的自定义路径 |
|
|
87
|
-
| `--log <name>` | 指定 API 跟踪日志基础名称(影响 `.claude-trace/tracelog/` 下文件名) |
|
|
88
|
-
| `--generate-html <input.jsonl> [output.html]` | 从 JSONL 文件生成 HTML 报告 |
|
|
89
104
|
| `--index` | 为 `.claude-trace/` 目录生成对话摘要和索引(会调用 Claude 产生额外 token 消耗) |
|
|
90
|
-
| `--extract-token` | 提取 OAuth token 并退出 |
|
|
91
105
|
| `--version, -v` | 显示版本信息 |
|
|
92
106
|
| `--help, -h` | 显示帮助信息 |
|
|
93
107
|
|
|
@@ -149,7 +163,7 @@ npm run dev
|
|
|
149
163
|
|
|
150
164
|
# 使用 tsx 直接运行 CLI(开发调试用)
|
|
151
165
|
npx tsx src/cli.ts --help
|
|
152
|
-
npx tsx src/cli.ts
|
|
166
|
+
npx tsx src/cli.ts -l --port 3001 --project /path/to/your/cc_workdir
|
|
153
167
|
|
|
154
168
|
# 功能ok后打包,包会出现在release目录
|
|
155
169
|
npm run package
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2BA,eAAO,MAAM,MAAM;;;;;;CAMT,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -38,7 +38,8 @@ exports.colors = void 0;
|
|
|
38
38
|
const child_process_1 = require("child_process");
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
|
-
const
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
42
|
+
const log_file_manager_1 = require("./log-file-manager");
|
|
42
43
|
const os = __importStar(require("os"));
|
|
43
44
|
/**
|
|
44
45
|
* 获取工具版本号
|
|
@@ -320,12 +321,132 @@ function getLoaderPath() {
|
|
|
320
321
|
}
|
|
321
322
|
return loaderPath;
|
|
322
323
|
}
|
|
323
|
-
|
|
324
|
-
|
|
324
|
+
/**
|
|
325
|
+
* 拷贝 Claude Code 日志文件到 cclog 目录
|
|
326
|
+
* @param sessionId 会话ID
|
|
327
|
+
* @param ccLogDir cclog 目录路径
|
|
328
|
+
*/
|
|
329
|
+
function copyCClogFile(sessionId, ccLogDir) {
|
|
330
|
+
// 检查是否启用了跟踪
|
|
331
|
+
if (!ccLogDir) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// 将当前会话对应的 cc 日志文件,拷贝到 .claude-trace/cclog 目录
|
|
335
|
+
try {
|
|
336
|
+
// 创建 LogFileManager 实例
|
|
337
|
+
const logFileManager = new log_file_manager_1.LogFileManager();
|
|
338
|
+
// 获取当前工作目录作为项目路径
|
|
339
|
+
const currentProjectPath = process.cwd();
|
|
340
|
+
// 通过 LogFileManager 解析源日志目录
|
|
341
|
+
const sourceLogDir = logFileManager.resolveLogDirectory(currentProjectPath);
|
|
342
|
+
// 构建源文件路径(假设源文件名为 sessionId.jsonl)
|
|
343
|
+
const sourceFile = path.join(sourceLogDir, `${sessionId}.jsonl`);
|
|
344
|
+
// 构建目标文件路径
|
|
345
|
+
const ccLogFile = path.join(ccLogDir, `${sessionId}.jsonl`);
|
|
346
|
+
// 检查源文件是否存在
|
|
347
|
+
if (!fs.existsSync(sourceFile)) {
|
|
348
|
+
console.log(`源CC日志文件不存在: ${sourceFile}`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// 确保目标 ccLogDir 目录存在(修复:之前未创建目录导致拷贝失败)
|
|
352
|
+
if (!fs.existsSync(ccLogDir)) {
|
|
353
|
+
fs.mkdirSync(ccLogDir, { recursive: true });
|
|
354
|
+
console.log(`已创建 cclog 目录: ${ccLogDir}`);
|
|
355
|
+
}
|
|
356
|
+
// 拷贝文件
|
|
357
|
+
fs.copyFileSync(sourceFile, ccLogFile);
|
|
358
|
+
console.log(`CC日志文件已从 ${sourceFile} 拷贝到 ${ccLogFile}`);
|
|
359
|
+
// 读取 sourceLogDir 目录下所有 agent_*.jsonl 文件,读取第一条记录的 sessionId,找到与 sessionId 变量值相同的文件,拷贝到 ccLogDir 目录
|
|
360
|
+
const files = fs.readdirSync(sourceLogDir).filter(file => file.startsWith('agent-') && file.endsWith('.jsonl'));
|
|
361
|
+
for (const file of files) {
|
|
362
|
+
const filePath = path.join(sourceLogDir, file);
|
|
363
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
364
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
365
|
+
if (lines.length > 0) {
|
|
366
|
+
try {
|
|
367
|
+
const firstRecord = JSON.parse(lines[0]);
|
|
368
|
+
const recordSessionId = firstRecord?.sessionId;
|
|
369
|
+
if (recordSessionId === sessionId) {
|
|
370
|
+
// 构建目标文件路径
|
|
371
|
+
const ccAgentLogFile = path.join(ccLogDir, file);
|
|
372
|
+
// 拷贝文件
|
|
373
|
+
fs.copyFileSync(filePath, ccAgentLogFile);
|
|
374
|
+
console.log(`SubAgent的CC日志文件已从 ${filePath} 拷贝到 ${ccAgentLogFile}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (parseError) {
|
|
378
|
+
// 静默处理解析错误,继续下一个文件
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// 兼容 subagents 日志的另一种存放方式:sourceLogDir/{sessionId}/subagents/ 目录
|
|
384
|
+
const subagentsDir = path.join(sourceLogDir, sessionId, 'subagents');
|
|
385
|
+
if (fs.existsSync(subagentsDir)) {
|
|
386
|
+
try {
|
|
387
|
+
const subagentFiles = fs.readdirSync(subagentsDir).filter(file => file.startsWith('agent-') && file.endsWith('.jsonl'));
|
|
388
|
+
// 只有当存在需要拷贝的文件时,才创建目标目录
|
|
389
|
+
if (subagentFiles.length > 0) {
|
|
390
|
+
// 创建目标目录结构:ccLogDir/{sessionId}/subagents/
|
|
391
|
+
const targetSubagentsDir = path.join(ccLogDir, sessionId, 'subagents');
|
|
392
|
+
if (!fs.existsSync(targetSubagentsDir)) {
|
|
393
|
+
fs.mkdirSync(targetSubagentsDir, { recursive: true });
|
|
394
|
+
}
|
|
395
|
+
for (const file of subagentFiles) {
|
|
396
|
+
const sourceFilePath = path.join(subagentsDir, file);
|
|
397
|
+
const targetFilePath = path.join(targetSubagentsDir, file);
|
|
398
|
+
// 直接拷贝文件,因为已经在正确的 sessionId 目录下
|
|
399
|
+
fs.copyFileSync(sourceFilePath, targetFilePath);
|
|
400
|
+
console.log(`SubAgent的CC日志文件已从 ${sourceFilePath} 拷贝到 ${targetFilePath}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
console.log(`处理subagents目录时出错: ${error}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.log(`拷贝CC日志文件时出错: ${error}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 根据 sessionId 重命名跟踪日志文件
|
|
415
|
+
* @param sessionId 会话ID
|
|
416
|
+
* @param traceLogFile 跟踪日志文件路径
|
|
417
|
+
* @returns 新的日志文件路径
|
|
418
|
+
*/
|
|
419
|
+
function renameTraceLogFileBySessionId(sessionId, traceLogFile) {
|
|
420
|
+
// 检查是否启用了跟踪
|
|
421
|
+
if (!traceLogFile) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const logDir = path.dirname(traceLogFile);
|
|
426
|
+
const newLogFile = path.join(logDir, `${sessionId}.jsonl`);
|
|
427
|
+
// 如果源文件不存在(如原生二进制模式下 interceptor 未运行,tracelog 未创建),直接跳过
|
|
428
|
+
if (!fs.existsSync(traceLogFile)) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
// 重命名文件
|
|
432
|
+
fs.renameSync(traceLogFile, newLogFile);
|
|
433
|
+
console.log(`Log file renamed from ${path.basename(traceLogFile)} to ${sessionId}.jsonl`);
|
|
434
|
+
return newLogFile;
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
console.log(`Error renaming log file: ${error}`);
|
|
438
|
+
return traceLogFile;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// 启动claude code
|
|
442
|
+
async function runClaudeWithInterception(claudeArgs = [], includeAllRequests = false, openInBrowser = false, customClaudePath, logBaseName, enableTrace = true) {
|
|
325
443
|
log("启动 Claude", "blue");
|
|
326
444
|
if (claudeArgs.length > 0) {
|
|
327
445
|
log(`Claude 参数: ${claudeArgs.join(" ")}`, "blue");
|
|
328
446
|
}
|
|
447
|
+
// 生成 UUID 格式的 sessionId
|
|
448
|
+
const sessionId = crypto.randomUUID();
|
|
449
|
+
log(`生成 SessionId: ${sessionId}`, "blue");
|
|
329
450
|
const claudePath = getClaudeAbsolutePath(customClaudePath);
|
|
330
451
|
log(`使用 Claude 二进制文件: ${claudePath}`, "blue");
|
|
331
452
|
let child;
|
|
@@ -338,7 +459,9 @@ async function runClaudeWithInterception(claudeArgs = [], includeAllRequests = f
|
|
|
338
459
|
log("使用 Node.js 启动 Claude(未启用跟踪)", "blue");
|
|
339
460
|
}
|
|
340
461
|
const loaderPath = getLoaderPath();
|
|
341
|
-
|
|
462
|
+
// 添加 --session-id 参数到 Claude 启动参数中
|
|
463
|
+
const claudeArgsWithSession = ["--session-id", sessionId, ...claudeArgs];
|
|
464
|
+
const spawnArgs = ["--require", loaderPath, claudePath, ...claudeArgsWithSession];
|
|
342
465
|
child = (0, child_process_1.spawn)("node", spawnArgs, {
|
|
343
466
|
env: {
|
|
344
467
|
...process.env,
|
|
@@ -362,8 +485,10 @@ async function runClaudeWithInterception(claudeArgs = [], includeAllRequests = f
|
|
|
362
485
|
console.log("");
|
|
363
486
|
// 给用户一点时间阅读提示信息
|
|
364
487
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
488
|
+
// 添加 --session-id 参数到 Claude 启动参数中
|
|
489
|
+
const claudeArgsWithSession = ["--session-id", sessionId, ...claudeArgs];
|
|
365
490
|
// 直接启动 Claude 二进制文件,不使用代理
|
|
366
|
-
child = (0, child_process_1.spawn)(claudePath,
|
|
491
|
+
child = (0, child_process_1.spawn)(claudePath, claudeArgsWithSession, {
|
|
367
492
|
env: {
|
|
368
493
|
...process.env,
|
|
369
494
|
},
|
|
@@ -418,118 +543,29 @@ async function runClaudeWithInterception(claudeArgs = [], includeAllRequests = f
|
|
|
418
543
|
log(`意外错误: ${err.message}`, "red");
|
|
419
544
|
process.exit(1);
|
|
420
545
|
}
|
|
421
|
-
|
|
422
|
-
// Scenario 2: --extract-token -> launch node with token interceptor and absolute path to claude
|
|
423
|
-
async function extractToken(customClaudePath) {
|
|
424
|
-
const claudePath = getClaudeAbsolutePath(customClaudePath);
|
|
425
|
-
// Log to stderr so it doesn't interfere with token output
|
|
426
|
-
console.error(`使用 Claude 二进制文件: ${claudePath}`);
|
|
427
|
-
// Create .claude-trace directory if it doesn't exist
|
|
428
|
-
const ccdebugDir = path.join(process.cwd(), ".claude-trace");
|
|
429
|
-
if (!fs.existsSync(ccdebugDir)) {
|
|
430
|
-
fs.mkdirSync(ccdebugDir, { recursive: true });
|
|
431
|
-
}
|
|
432
|
-
// Token file location
|
|
433
|
-
const tokenFile = path.join(ccdebugDir, "token.txt");
|
|
434
|
-
// Use the token extractor directly without copying
|
|
435
|
-
const tokenExtractorPath = path.join(__dirname, "token-extractor.js");
|
|
436
|
-
if (!fs.existsSync(tokenExtractorPath)) {
|
|
437
|
-
log(`未找到令牌提取器: ${tokenExtractorPath}`, "red");
|
|
438
|
-
process.exit(1);
|
|
439
|
-
}
|
|
440
|
-
const cleanup = () => {
|
|
441
|
-
try {
|
|
442
|
-
if (fs.existsSync(tokenFile))
|
|
443
|
-
fs.unlinkSync(tokenFile);
|
|
444
|
-
}
|
|
445
|
-
catch (e) {
|
|
446
|
-
// Ignore cleanup errors
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
// Launch node with token interceptor and absolute path to claude
|
|
450
|
-
const { ANTHROPIC_API_KEY, ...envWithoutApiKey } = process.env;
|
|
451
|
-
const child = (0, child_process_1.spawn)("node", ["--require", tokenExtractorPath, claudePath, "-p", "hello"], {
|
|
452
|
-
env: {
|
|
453
|
-
...envWithoutApiKey,
|
|
454
|
-
NODE_TLS_REJECT_UNAUTHORIZED: "0",
|
|
455
|
-
CLAUDE_TRACE_TOKEN_FILE: tokenFile,
|
|
456
|
-
},
|
|
457
|
-
stdio: "inherit", // Suppress all output from Claude
|
|
458
|
-
cwd: process.cwd(),
|
|
459
|
-
});
|
|
460
|
-
// Set a timeout to avoid hanging
|
|
461
|
-
const timeout = setTimeout(() => {
|
|
462
|
-
child.kill();
|
|
463
|
-
cleanup();
|
|
464
|
-
console.error("超时: 30 秒内未找到令牌");
|
|
465
|
-
process.exit(1);
|
|
466
|
-
}, 30000);
|
|
467
|
-
// Handle child process events
|
|
468
|
-
child.on("error", (error) => {
|
|
469
|
-
clearTimeout(timeout);
|
|
470
|
-
cleanup();
|
|
471
|
-
console.error(`启动 Claude 时出错: ${error.message}`);
|
|
472
|
-
process.exit(1);
|
|
473
|
-
});
|
|
474
|
-
child.on("exit", () => {
|
|
475
|
-
clearTimeout(timeout);
|
|
476
|
-
try {
|
|
477
|
-
if (fs.existsSync(tokenFile)) {
|
|
478
|
-
const token = fs.readFileSync(tokenFile, "utf-8").trim();
|
|
479
|
-
cleanup();
|
|
480
|
-
if (token) {
|
|
481
|
-
// Only output the token, nothing else
|
|
482
|
-
console.log(token);
|
|
483
|
-
process.exit(0);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
catch (e) {
|
|
488
|
-
// File doesn't exist or read error
|
|
489
|
-
}
|
|
490
|
-
cleanup();
|
|
491
|
-
console.error("未找到授权令牌");
|
|
492
|
-
process.exit(1);
|
|
493
|
-
});
|
|
494
|
-
// Check for token file periodically
|
|
495
|
-
const checkToken = setInterval(() => {
|
|
496
|
-
try {
|
|
497
|
-
if (fs.existsSync(tokenFile)) {
|
|
498
|
-
const token = fs.readFileSync(tokenFile, "utf-8").trim();
|
|
499
|
-
if (token) {
|
|
500
|
-
clearTimeout(timeout);
|
|
501
|
-
clearInterval(checkToken);
|
|
502
|
-
child.kill();
|
|
503
|
-
cleanup();
|
|
504
|
-
// Only output the token, nothing else
|
|
505
|
-
console.log(token);
|
|
506
|
-
process.exit(0);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
catch (e) {
|
|
511
|
-
// Ignore read errors, keep trying
|
|
512
|
-
}
|
|
513
|
-
}, 500);
|
|
514
|
-
}
|
|
515
|
-
// Scenario 3: --generate-html input.jsonl output.html
|
|
516
|
-
async function generateHTMLFromCLI(inputFile, outputFile, includeAllRequests = false, openInBrowser = false) {
|
|
546
|
+
// Claude 执行完成后,处理 CC 日志文件(不管是否启用了跟踪)
|
|
517
547
|
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
548
|
+
console.log("\nClaude 执行完成,处理 CC 日志文件...");
|
|
549
|
+
// 构建日志文件路径
|
|
550
|
+
const traceHomeDir = ".claude-trace";
|
|
551
|
+
const traceLogDir = path.join(traceHomeDir, 'tracelog');
|
|
552
|
+
const ccLogDir = path.join(traceHomeDir, 'cclog');
|
|
553
|
+
// 生成日志文件名
|
|
554
|
+
const fileBaseName = logBaseName || `log-${new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, -5)}`;
|
|
555
|
+
const traceLogFile = enableTrace ? path.join(traceLogDir, `${fileBaseName}.jsonl`) : null;
|
|
556
|
+
// 直接使用之前生成的 sessionId
|
|
557
|
+
console.log(`使用 SessionId: ${sessionId} 处理日志文件`);
|
|
558
|
+
// 将当前会话对应的 cc 日志文件,拷贝到 .claude-trace/cclog 目录
|
|
559
|
+
copyCClogFile(sessionId, ccLogDir);
|
|
560
|
+
// 根据 sessionId 重命名跟踪日志文件
|
|
561
|
+
renameTraceLogFileBySessionId(sessionId, traceLogFile);
|
|
525
562
|
}
|
|
526
563
|
catch (error) {
|
|
527
|
-
|
|
528
|
-
log(
|
|
529
|
-
process.exit(1);
|
|
564
|
+
// 静默处理错误,不影响主流程
|
|
565
|
+
console.log(`处理CC日志时出错: ${error}`);
|
|
530
566
|
}
|
|
531
567
|
}
|
|
532
|
-
//
|
|
568
|
+
// 启动web工具
|
|
533
569
|
async function startWebServer(port, projectDir) {
|
|
534
570
|
try {
|
|
535
571
|
// 使用 require 导入 web server 模块
|
|
@@ -561,20 +597,6 @@ async function startWebServer(port, projectDir) {
|
|
|
561
597
|
process.exit(1);
|
|
562
598
|
}
|
|
563
599
|
}
|
|
564
|
-
// Scenario 4: --index
|
|
565
|
-
async function generateIndex() {
|
|
566
|
-
try {
|
|
567
|
-
const { IndexGenerator } = await Promise.resolve().then(() => __importStar(require("./index-generator")));
|
|
568
|
-
const indexGenerator = new IndexGenerator();
|
|
569
|
-
await indexGenerator.generateIndex();
|
|
570
|
-
process.exit(0);
|
|
571
|
-
}
|
|
572
|
-
catch (error) {
|
|
573
|
-
const err = error;
|
|
574
|
-
log(`错误: ${err.message}`, "red");
|
|
575
|
-
process.exit(1);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
600
|
// Main entry point
|
|
579
601
|
async function main() {
|
|
580
602
|
const args = process.argv.slice(2);
|
|
@@ -601,7 +623,7 @@ async function main() {
|
|
|
601
623
|
process.exit(0);
|
|
602
624
|
}
|
|
603
625
|
// Check for trace flag
|
|
604
|
-
const enableTrace = claudeTraceArgs.includes("--
|
|
626
|
+
const enableTrace = !claudeTraceArgs.includes("--notrace");
|
|
605
627
|
// Check for include all requests flag
|
|
606
628
|
const includeAllRequests = claudeTraceArgs.includes("--include-all-requests");
|
|
607
629
|
// Check for no-open flag (inverted logic - open by default)
|
|
@@ -633,37 +655,6 @@ async function main() {
|
|
|
633
655
|
if (projectIndex !== -1 && claudeTraceArgs[projectIndex + 1]) {
|
|
634
656
|
serveProjectDir = claudeTraceArgs[projectIndex + 1];
|
|
635
657
|
}
|
|
636
|
-
// Scenario 2: --extract-token
|
|
637
|
-
if (claudeTraceArgs.includes("--extract-token")) {
|
|
638
|
-
await extractToken(customClaudePath);
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
// Scenario 3: --generate-html input.jsonl output.html
|
|
642
|
-
if (claudeTraceArgs.includes("--generate-html")) {
|
|
643
|
-
const flagIndex = claudeTraceArgs.indexOf("--generate-html");
|
|
644
|
-
const inputFile = claudeTraceArgs[flagIndex + 1];
|
|
645
|
-
// Find is next argument that's not a flag as the output file
|
|
646
|
-
let outputFile;
|
|
647
|
-
for (let i = flagIndex + 2; i < claudeTraceArgs.length; i++) {
|
|
648
|
-
const arg = claudeTraceArgs[i];
|
|
649
|
-
if (!arg.startsWith("--")) {
|
|
650
|
-
outputFile = arg;
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (!inputFile) {
|
|
655
|
-
log(`--generate-html 缺少输入文件`, "red");
|
|
656
|
-
log(`用法: ccdebug --generate-html input.jsonl [output.html]`, "yellow");
|
|
657
|
-
process.exit(1);
|
|
658
|
-
}
|
|
659
|
-
await generateHTMLFromCLI(inputFile, outputFile, includeAllRequests, openInBrowser);
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
// Scenario 4: --index
|
|
663
|
-
if (claudeTraceArgs.includes("--index")) {
|
|
664
|
-
await generateIndex();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
658
|
// Scenario 5: --serve, --log, -l
|
|
668
659
|
const hasServeFlag = claudeTraceArgs.includes("--serve");
|
|
669
660
|
const hasLogFlag = claudeTraceArgs.includes("--log") || claudeTraceArgs.includes("-l");
|
package/dist/interceptor.d.ts
CHANGED
|
@@ -31,9 +31,6 @@ export declare class ClaudeTrafficLogger {
|
|
|
31
31
|
private writePairToLog;
|
|
32
32
|
private generateHTML;
|
|
33
33
|
cleanup(): void;
|
|
34
|
-
private getSessionIdFromLog;
|
|
35
|
-
private copyCClogFile;
|
|
36
|
-
private renameTraceLogFileBySessionId;
|
|
37
34
|
getStats(): {
|
|
38
35
|
totalPairs: number;
|
|
39
36
|
pendingRequests: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAOA,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;IAmE1C,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;IA+FvB,kBAAkB,IAAI,IAAI;IAmDjC,OAAO,CAAC,oBAAoB;IAuE5B,OAAO,CAAC,mBAAmB;YAab,2BAA2B;YAiB3B,cAAc;YAcd,YAAY;IAmBnB,OAAO,IAAI,IAAI;IAuCf,QAAQ;;;;;;CAQf;AAQD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,mBAAmB,CA8BrF;AAED,wBAAgB,SAAS,IAAI,mBAAmB,GAAG,IAAI,CAEtD"}
|
package/dist/interceptor.js
CHANGED
|
@@ -9,7 +9,6 @@ exports.getLogger = getLogger;
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const html_generator_1 = require("./html-generator");
|
|
12
|
-
const log_file_manager_1 = require("./log-file-manager");
|
|
13
12
|
class ClaudeTrafficLogger {
|
|
14
13
|
constructor(config = {}) {
|
|
15
14
|
this.pendingRequests = new Map();
|
|
@@ -433,14 +432,6 @@ class ClaudeTrafficLogger {
|
|
|
433
432
|
}
|
|
434
433
|
this.pendingRequests.clear();
|
|
435
434
|
console.log(`Cleanup complete. Logged ${this.pairs.length} pairs`);
|
|
436
|
-
//获取cc会话的sessionid
|
|
437
|
-
let sessionId = this.getSessionIdFromLog();
|
|
438
|
-
if (sessionId != '') {
|
|
439
|
-
//将当前会话对应的cc日志文件,拷贝到.claude-trace/cclog目录
|
|
440
|
-
this.copyCClogFile(sessionId);
|
|
441
|
-
// Rename log file based on sessionid from first record
|
|
442
|
-
this.renameTraceLogFileBySessionId(sessionId);
|
|
443
|
-
}
|
|
444
435
|
// Open browser if requested
|
|
445
436
|
// const shouldOpenBrowser = process.env.CLAUDE_TRACE_OPEN_BROWSER === "true";
|
|
446
437
|
// if (shouldOpenBrowser && fs.existsSync(this.htmlFile)) {
|
|
@@ -452,133 +443,6 @@ class ClaudeTrafficLogger {
|
|
|
452
443
|
// }
|
|
453
444
|
// }
|
|
454
445
|
}
|
|
455
|
-
getSessionIdFromLog() {
|
|
456
|
-
// 检查是否启用了跟踪
|
|
457
|
-
if (!this.traceLogFile) {
|
|
458
|
-
return '';
|
|
459
|
-
}
|
|
460
|
-
// Check if log file exists
|
|
461
|
-
if (!fs_1.default.existsSync(this.traceLogFile)) {
|
|
462
|
-
console.log("获取sessionId错误:Log file does not exist");
|
|
463
|
-
return '';
|
|
464
|
-
}
|
|
465
|
-
// Read the first line of the JSONL file
|
|
466
|
-
const fileContent = fs_1.default.readFileSync(this.traceLogFile, 'utf-8');
|
|
467
|
-
const lines = fileContent.split('\n').filter(line => line.trim());
|
|
468
|
-
if (lines.length === 0) {
|
|
469
|
-
console.log("获取sessionId错误:Log file is empty");
|
|
470
|
-
return '';
|
|
471
|
-
}
|
|
472
|
-
// 循环读取日志,直到找到user_id为止
|
|
473
|
-
let userId = null;
|
|
474
|
-
for (const line of lines) {
|
|
475
|
-
const record = JSON.parse(line);
|
|
476
|
-
userId = record?.request?.body?.metadata?.user_id;
|
|
477
|
-
if (userId) {
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (!userId) {
|
|
482
|
-
console.log("获取sessionId错误:No user_id found in any record");
|
|
483
|
-
return '';
|
|
484
|
-
}
|
|
485
|
-
// Extract sessionid from user_id (format: xxxx_session_{sessionid})
|
|
486
|
-
const sessionMatch = userId.match(/_session_([^_]+)$/);
|
|
487
|
-
if (!sessionMatch || !sessionMatch[1]) {
|
|
488
|
-
console.log(`获取sessionId错误:No sessionid found in user_id: ${userId}`);
|
|
489
|
-
return '';
|
|
490
|
-
}
|
|
491
|
-
return sessionMatch[1];
|
|
492
|
-
}
|
|
493
|
-
copyCClogFile(sessionId) {
|
|
494
|
-
// 检查是否启用了跟踪
|
|
495
|
-
if (!this.ccLogDir) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
//将当前会话对应的cc日志文件,拷贝到.claude-trace/cclog目录
|
|
499
|
-
try {
|
|
500
|
-
// 创建LogFileManager实例
|
|
501
|
-
const logFileManager = new log_file_manager_1.LogFileManager();
|
|
502
|
-
// 获取当前工作目录作为项目路径
|
|
503
|
-
const currentProjectPath = process.cwd();
|
|
504
|
-
// 通过LogFileManager解析源日志目录
|
|
505
|
-
const sourceLogDir = logFileManager.resolveLogDirectory(currentProjectPath);
|
|
506
|
-
// 构建源文件路径(假设源文件名为sessionId.jsonl)
|
|
507
|
-
const sourceFile = path_1.default.join(sourceLogDir, `${sessionId}.jsonl`);
|
|
508
|
-
// 构建目标文件路径
|
|
509
|
-
this.ccLogFile = path_1.default.join(this.ccLogDir, `${sessionId}.jsonl`);
|
|
510
|
-
// 检查源文件是否存在
|
|
511
|
-
if (!fs_1.default.existsSync(sourceFile)) {
|
|
512
|
-
console.log(`源CC日志文件不存在: ${sourceFile}`);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
// 拷贝文件
|
|
516
|
-
fs_1.default.copyFileSync(sourceFile, this.ccLogFile);
|
|
517
|
-
console.log(`CC日志文件已从 ${sourceFile} 拷贝到 ${this.ccLogFile}`);
|
|
518
|
-
// 读取sourceLogDir目录下所有agent_*.jsonl文件,读取第一条记录的sessionId,找到与sessionId变量值相同的文件,拷贝到ccLogDir目录
|
|
519
|
-
const files = fs_1.default.readdirSync(sourceLogDir).filter(file => file.startsWith('agent-') && file.endsWith('.jsonl'));
|
|
520
|
-
for (const file of files) {
|
|
521
|
-
const filePath = path_1.default.join(sourceLogDir, file);
|
|
522
|
-
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
523
|
-
const lines = content.split('\n').filter(line => line.trim());
|
|
524
|
-
if (lines.length > 0) {
|
|
525
|
-
try {
|
|
526
|
-
const firstRecord = JSON.parse(lines[0]);
|
|
527
|
-
const recordSessionId = firstRecord?.sessionId;
|
|
528
|
-
if (recordSessionId === sessionId) {
|
|
529
|
-
// 构建目标文件路径
|
|
530
|
-
const ccAgentLogFile = path_1.default.join(this.ccLogDir, file);
|
|
531
|
-
// 拷贝文件
|
|
532
|
-
fs_1.default.copyFileSync(filePath, ccAgentLogFile);
|
|
533
|
-
console.log(`SubAgent的CC日志文件已从 ${filePath} 拷贝到 ${ccAgentLogFile}`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
catch (parseError) {
|
|
537
|
-
// 静默处理解析错误,继续下一个文件
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
// 兼容subagents日志的另一种存放方式:sourceLogDir/{sessionId}/subagents/目录
|
|
543
|
-
const subagentsDir = path_1.default.join(sourceLogDir, sessionId, 'subagents');
|
|
544
|
-
if (fs_1.default.existsSync(subagentsDir)) {
|
|
545
|
-
try {
|
|
546
|
-
const subagentFiles = fs_1.default.readdirSync(subagentsDir).filter(file => file.startsWith('agent-') && file.endsWith('.jsonl'));
|
|
547
|
-
for (const file of subagentFiles) {
|
|
548
|
-
const sourceFilePath = path_1.default.join(subagentsDir, file);
|
|
549
|
-
const targetFilePath = path_1.default.join(this.ccLogDir, file);
|
|
550
|
-
// 直接拷贝文件,因为已经在正确的sessionId目录下
|
|
551
|
-
fs_1.default.copyFileSync(sourceFilePath, targetFilePath);
|
|
552
|
-
console.log(`SubAgent的CC日志文件已从 ${sourceFilePath} 拷贝到 ${targetFilePath}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
console.log(`处理subagents目录时出错: ${error}`);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
catch (error) {
|
|
561
|
-
console.log(`拷贝CC日志文件时出错: ${error}`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
renameTraceLogFileBySessionId(sessionId) {
|
|
565
|
-
// 检查是否启用了跟踪
|
|
566
|
-
if (!this.traceLogFile) {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
try {
|
|
570
|
-
const logDir = path_1.default.dirname(this.traceLogFile);
|
|
571
|
-
const newLogFile = path_1.default.join(logDir, `${sessionId}.jsonl`);
|
|
572
|
-
// Rename the file
|
|
573
|
-
fs_1.default.renameSync(this.traceLogFile, newLogFile);
|
|
574
|
-
console.log(`Log file renamed from ${path_1.default.basename(this.traceLogFile)} to ${sessionId}.jsonl`);
|
|
575
|
-
// Update the logFile path for future reference
|
|
576
|
-
this.traceLogFile = newLogFile;
|
|
577
|
-
}
|
|
578
|
-
catch (error) {
|
|
579
|
-
console.log(`Error renaming log file: ${error}`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
446
|
getStats() {
|
|
583
447
|
return {
|
|
584
448
|
totalPairs: this.pairs.length,
|