@adversity/coding-tool-x 2.4.0 → 2.4.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/CHANGELOG.md +17 -0
- package/dist/web/assets/icons-Dom8a0SN.js +1 -0
- package/dist/web/assets/index-CQeUIH7E.css +41 -0
- package/dist/web/assets/index-YrjlFzC4.js +14 -0
- package/dist/web/assets/naive-ui-BjMHakwv.js +1 -0
- package/dist/web/assets/vendors-DtJKdpSj.js +7 -0
- package/dist/web/assets/vue-vendor-VFuFB5f4.js +44 -0
- package/dist/web/index.html +6 -2
- package/package.json +2 -2
- package/src/commands/export-config.js +205 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/projects.js +2 -2
- package/src/server/api/sessions.js +70 -70
- package/src/server/api/skills.js +206 -0
- package/src/server/api/terminal.js +26 -0
- package/src/server/index.js +3 -0
- package/src/server/services/config-export-service.js +229 -23
- package/src/server/services/config-sync-service.js +515 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/pty-manager.js +35 -1
- package/src/server/services/sessions.js +122 -44
- package/src/server/services/skill-service.js +252 -2
- package/src/server/services/workspace-service.js +36 -18
- package/src/server/websocket-server.js +4 -1
- package/dist/web/assets/index-Bu1oPcKu.js +0 -4009
- package/dist/web/assets/index-XSok7-mN.css +0 -41
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const archiver = require('archiver');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 导出 Claude Code 配置为 ZIP 压缩包
|
|
9
|
+
* 包含:全局 CLAUDE.md、项目 CLAUDE.md、settings.json、MCP 服务器配置、Skills 等
|
|
10
|
+
*/
|
|
11
|
+
async function exportConfig(options = {}) {
|
|
12
|
+
const { output = 'claude-config.zip', includeProjects = true } = options;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
console.log(chalk.blue('🚀 开始导出 Claude Code 配置...'));
|
|
16
|
+
|
|
17
|
+
const homeDir = os.homedir();
|
|
18
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
19
|
+
const currentDir = process.cwd();
|
|
20
|
+
|
|
21
|
+
// 检查 .claude 目录是否存在
|
|
22
|
+
if (!fs.existsSync(claudeDir)) {
|
|
23
|
+
console.log(chalk.red('❌ 未找到 .claude 配置目录'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 创建输出文件流
|
|
28
|
+
const outputPath = path.resolve(currentDir, output);
|
|
29
|
+
const outputStream = fs.createWriteStream(outputPath);
|
|
30
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
31
|
+
|
|
32
|
+
// 监听错误
|
|
33
|
+
archive.on('error', (err) => {
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// 监听完成
|
|
38
|
+
archive.on('end', () => {
|
|
39
|
+
console.log(chalk.green(`\n✅ 配置导出成功!`));
|
|
40
|
+
console.log(chalk.gray(` 文件位置: ${outputPath}`));
|
|
41
|
+
console.log(chalk.gray(` 文件大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 连接流
|
|
45
|
+
archive.pipe(outputStream);
|
|
46
|
+
|
|
47
|
+
// 收集配置清单
|
|
48
|
+
const manifest = {
|
|
49
|
+
exportTime: new Date().toISOString(),
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
globalConfig: {},
|
|
52
|
+
projectConfig: {},
|
|
53
|
+
enabledConfigs: []
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// 1. 导出全局 CLAUDE.md
|
|
57
|
+
const globalClaudeMd = path.join(claudeDir, 'CLAUDE.md');
|
|
58
|
+
if (fs.existsSync(globalClaudeMd)) {
|
|
59
|
+
archive.file(globalClaudeMd, { name: 'global/CLAUDE.md' });
|
|
60
|
+
manifest.globalConfig.claudeMd = true;
|
|
61
|
+
manifest.enabledConfigs.push('global/CLAUDE.md');
|
|
62
|
+
console.log(chalk.gray(' ✓ 全局 CLAUDE.md'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. 导出 settings.json
|
|
66
|
+
const settingsJson = path.join(claudeDir, 'settings.json');
|
|
67
|
+
if (fs.existsSync(settingsJson)) {
|
|
68
|
+
archive.file(settingsJson, { name: 'global/settings.json' });
|
|
69
|
+
manifest.globalConfig.settings = true;
|
|
70
|
+
manifest.enabledConfigs.push('global/settings.json');
|
|
71
|
+
console.log(chalk.gray(' ✓ settings.json'));
|
|
72
|
+
|
|
73
|
+
// 解析 MCP 服务器配置
|
|
74
|
+
try {
|
|
75
|
+
const settings = JSON.parse(fs.readFileSync(settingsJson, 'utf-8'));
|
|
76
|
+
if (settings.mcpServers) {
|
|
77
|
+
manifest.globalConfig.mcpServers = Object.keys(settings.mcpServers);
|
|
78
|
+
console.log(chalk.gray(` - MCP 服务器: ${manifest.globalConfig.mcpServers.join(', ')}`));
|
|
79
|
+
}
|
|
80
|
+
if (settings.hooks) {
|
|
81
|
+
manifest.globalConfig.hooks = Object.keys(settings.hooks);
|
|
82
|
+
}
|
|
83
|
+
if (settings.statusLine) {
|
|
84
|
+
manifest.globalConfig.statusLine = true;
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.log(chalk.yellow(' ⚠ settings.json 解析失败'));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. 导出 Skills
|
|
92
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
93
|
+
if (fs.existsSync(skillsDir)) {
|
|
94
|
+
const skills = fs.readdirSync(skillsDir).filter(f => !f.startsWith('.'));
|
|
95
|
+
if (skills.length > 0) {
|
|
96
|
+
archive.directory(skillsDir, 'global/skills');
|
|
97
|
+
manifest.globalConfig.skills = skills;
|
|
98
|
+
manifest.enabledConfigs.push('global/skills');
|
|
99
|
+
console.log(chalk.gray(` ✓ Skills (${skills.length} 个)`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 4. 导出当前项目配置
|
|
104
|
+
if (includeProjects) {
|
|
105
|
+
// 项目级 CLAUDE.md
|
|
106
|
+
const projectClaudeMd = path.join(currentDir, 'CLAUDE.md');
|
|
107
|
+
if (fs.existsSync(projectClaudeMd)) {
|
|
108
|
+
archive.file(projectClaudeMd, { name: 'project/CLAUDE.md' });
|
|
109
|
+
manifest.projectConfig.claudeMd = true;
|
|
110
|
+
manifest.enabledConfigs.push('project/CLAUDE.md');
|
|
111
|
+
console.log(chalk.gray(' ✓ 项目 CLAUDE.md'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 项目级 settings.local.json
|
|
115
|
+
const projectSettings = path.join(currentDir, '.claude', 'settings.local.json');
|
|
116
|
+
if (fs.existsSync(projectSettings)) {
|
|
117
|
+
archive.file(projectSettings, { name: 'project/.claude/settings.local.json' });
|
|
118
|
+
manifest.projectConfig.settingsLocal = true;
|
|
119
|
+
manifest.enabledConfigs.push('project/.claude/settings.local.json');
|
|
120
|
+
console.log(chalk.gray(' ✓ 项目 settings.local.json'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 项目级 workspace CLAUDE.md
|
|
124
|
+
const workspaceClaudeMd = path.join(homeDir, 'workspace', 'CLAUDE.md');
|
|
125
|
+
if (fs.existsSync(workspaceClaudeMd)) {
|
|
126
|
+
archive.file(workspaceClaudeMd, { name: 'workspace/CLAUDE.md' });
|
|
127
|
+
manifest.projectConfig.workspaceClaudeMd = true;
|
|
128
|
+
manifest.enabledConfigs.push('workspace/CLAUDE.md');
|
|
129
|
+
console.log(chalk.gray(' ✓ workspace CLAUDE.md'));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 5. 添加配置清单
|
|
134
|
+
archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' });
|
|
135
|
+
console.log(chalk.gray(' ✓ manifest.json'));
|
|
136
|
+
|
|
137
|
+
// 6. 添加 README
|
|
138
|
+
const readme = generateReadme(manifest);
|
|
139
|
+
archive.append(readme, { name: 'README.md' });
|
|
140
|
+
console.log(chalk.gray(' ✓ README.md'));
|
|
141
|
+
|
|
142
|
+
// 完成打包
|
|
143
|
+
await archive.finalize();
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error(chalk.red('❌ 导出失败:'), error.message);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 生成配置包说明文档
|
|
153
|
+
*/
|
|
154
|
+
function generateReadme(manifest) {
|
|
155
|
+
return `# Claude Code 配置包
|
|
156
|
+
|
|
157
|
+
导出时间: ${new Date(manifest.exportTime).toLocaleString('zh-CN')}
|
|
158
|
+
版本: ${manifest.version}
|
|
159
|
+
|
|
160
|
+
## 📦 包含内容
|
|
161
|
+
|
|
162
|
+
### 全局配置
|
|
163
|
+
${manifest.globalConfig.claudeMd ? '- ✅ 全局 CLAUDE.md(AI 行为指令)' : ''}
|
|
164
|
+
${manifest.globalConfig.settings ? '- ✅ settings.json(包含 MCP 服务器、Hooks 等)' : ''}
|
|
165
|
+
${manifest.globalConfig.mcpServers ? ` - MCP 服务器: ${manifest.globalConfig.mcpServers.join(', ')}` : ''}
|
|
166
|
+
${manifest.globalConfig.hooks ? ` - Hooks: ${manifest.globalConfig.hooks.join(', ')}` : ''}
|
|
167
|
+
${manifest.globalConfig.skills ? `- ✅ Skills (${manifest.globalConfig.skills.length} 个): ${manifest.globalConfig.skills.join(', ')}` : ''}
|
|
168
|
+
|
|
169
|
+
### 项目配置
|
|
170
|
+
${manifest.projectConfig.claudeMd ? '- ✅ 项目 CLAUDE.md' : ''}
|
|
171
|
+
${manifest.projectConfig.settingsLocal ? '- ✅ 项目 settings.local.json' : ''}
|
|
172
|
+
${manifest.projectConfig.workspaceClaudeMd ? '- ✅ workspace CLAUDE.md' : ''}
|
|
173
|
+
|
|
174
|
+
## 📥 如何导入
|
|
175
|
+
|
|
176
|
+
### 方法 1:手动导入
|
|
177
|
+
1. 解压此 ZIP 文件
|
|
178
|
+
2. 复制 \`global/\` 内容到 \`~/.claude/\`
|
|
179
|
+
3. 复制 \`project/\` 内容到你的项目目录
|
|
180
|
+
4. 复制 \`workspace/\` 内容到 \`~/workspace/\`
|
|
181
|
+
|
|
182
|
+
### 方法 2:使用命令(计划中)
|
|
183
|
+
\`\`\`bash
|
|
184
|
+
ctx import-config claude-config.zip
|
|
185
|
+
\`\`\`
|
|
186
|
+
|
|
187
|
+
## ⚠️ 注意事项
|
|
188
|
+
|
|
189
|
+
1. **备份现有配置**:导入前请备份现有的 \`.claude\` 目录
|
|
190
|
+
2. **敏感信息**:此配置包可能包含 API Keys、代理配置等敏感信息,请妥善保管
|
|
191
|
+
3. **路径适配**:某些路径可能需要根据你的系统调整(如 hooks 中的脚本路径)
|
|
192
|
+
4. **MCP 服务器**:需要确保目标环境已安装对应的 MCP 服务器
|
|
193
|
+
|
|
194
|
+
## 📝 配置清单
|
|
195
|
+
|
|
196
|
+
已启用的配置项:
|
|
197
|
+
${manifest.enabledConfigs.map(c => `- ${c}`).join('\n')}
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
生成工具: Coding-Tool-X (ctx)
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { exportConfig };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置同步 API 路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const { ConfigSyncService } = require('../services/config-sync-service');
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const configSyncService = new ConfigSyncService();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 获取可同步的配置列表
|
|
13
|
+
* GET /api/config-sync/available
|
|
14
|
+
* Query: source=global|workspace, projectPath=...
|
|
15
|
+
*/
|
|
16
|
+
router.get('/available', (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const { source = 'global', projectPath } = req.query;
|
|
19
|
+
|
|
20
|
+
if (source === 'workspace' && !projectPath) {
|
|
21
|
+
return res.status(400).json({
|
|
22
|
+
success: false,
|
|
23
|
+
message: '获取工作区配置需要指定 projectPath'
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const configs = configSyncService.getAvailableConfigs(source, projectPath);
|
|
28
|
+
|
|
29
|
+
res.json({
|
|
30
|
+
success: true,
|
|
31
|
+
source,
|
|
32
|
+
configs
|
|
33
|
+
});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('[ConfigSync API] Get available configs error:', err);
|
|
36
|
+
res.status(500).json({
|
|
37
|
+
success: false,
|
|
38
|
+
message: err.message
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取同步统计信息
|
|
45
|
+
* GET /api/config-sync/stats
|
|
46
|
+
* Query: projectPath=...
|
|
47
|
+
*/
|
|
48
|
+
router.get('/stats', (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const { projectPath } = req.query;
|
|
51
|
+
const stats = configSyncService.getStats(projectPath);
|
|
52
|
+
|
|
53
|
+
res.json({
|
|
54
|
+
success: true,
|
|
55
|
+
stats
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[ConfigSync API] Get stats error:', err);
|
|
59
|
+
res.status(500).json({
|
|
60
|
+
success: false,
|
|
61
|
+
message: err.message
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 预览同步结果
|
|
68
|
+
* POST /api/config-sync/preview
|
|
69
|
+
* Body: { source, target, configTypes, projectPath, selectedItems }
|
|
70
|
+
*/
|
|
71
|
+
router.post('/preview', (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
const { source, target, configTypes, projectPath, selectedItems } = req.body;
|
|
74
|
+
|
|
75
|
+
if (!source || !target) {
|
|
76
|
+
return res.status(400).json({
|
|
77
|
+
success: false,
|
|
78
|
+
message: '请指定源和目标'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!configTypes || configTypes.length === 0) {
|
|
83
|
+
return res.status(400).json({
|
|
84
|
+
success: false,
|
|
85
|
+
message: '请选择要同步的配置类型'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const preview = configSyncService.previewSync({
|
|
90
|
+
source,
|
|
91
|
+
target,
|
|
92
|
+
configTypes,
|
|
93
|
+
projectPath,
|
|
94
|
+
selectedItems
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
res.json({
|
|
98
|
+
success: true,
|
|
99
|
+
preview
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error('[ConfigSync API] Preview sync error:', err);
|
|
103
|
+
res.status(500).json({
|
|
104
|
+
success: false,
|
|
105
|
+
message: err.message
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 执行同步
|
|
112
|
+
* POST /api/config-sync/execute
|
|
113
|
+
* Body: { source, target, configTypes, projectPath, selectedItems, overwrite }
|
|
114
|
+
*/
|
|
115
|
+
router.post('/execute', (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const { source, target, configTypes, projectPath, selectedItems, overwrite = false } = req.body;
|
|
118
|
+
|
|
119
|
+
if (!source || !target) {
|
|
120
|
+
return res.status(400).json({
|
|
121
|
+
success: false,
|
|
122
|
+
message: '请指定源和目标'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!configTypes || configTypes.length === 0) {
|
|
127
|
+
return res.status(400).json({
|
|
128
|
+
success: false,
|
|
129
|
+
message: '请选择要同步的配置类型'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = configSyncService.executeSync({
|
|
134
|
+
source,
|
|
135
|
+
target,
|
|
136
|
+
configTypes,
|
|
137
|
+
projectPath,
|
|
138
|
+
selectedItems,
|
|
139
|
+
overwrite
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
res.json({
|
|
143
|
+
success: true,
|
|
144
|
+
result
|
|
145
|
+
});
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error('[ConfigSync API] Execute sync error:', err);
|
|
148
|
+
res.status(500).json({
|
|
149
|
+
success: false,
|
|
150
|
+
message: err.message
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
module.exports = router;
|
|
@@ -4,9 +4,9 @@ const { getProjectsWithStats, saveProjectOrder, getProjectOrder, deleteProject }
|
|
|
4
4
|
|
|
5
5
|
module.exports = (config) => {
|
|
6
6
|
// GET /api/projects - Get all projects with stats
|
|
7
|
-
router.get('/', (req, res) => {
|
|
7
|
+
router.get('/', async (req, res) => {
|
|
8
8
|
try {
|
|
9
|
-
const projects = getProjectsWithStats(config);
|
|
9
|
+
const projects = await getProjectsWithStats(config);
|
|
10
10
|
const order = getProjectOrder(config);
|
|
11
11
|
|
|
12
12
|
// Sort projects by saved order
|
|
@@ -45,10 +45,10 @@ module.exports = (config) => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// GET /api/sessions/:projectName - Get sessions for a project
|
|
48
|
-
router.get('/:projectName', (req, res) => {
|
|
48
|
+
router.get('/:projectName', async (req, res) => {
|
|
49
49
|
try {
|
|
50
50
|
const { projectName } = req.params;
|
|
51
|
-
const result = getSessionsForProject(config, projectName);
|
|
51
|
+
const result = await getSessionsForProject(config, projectName);
|
|
52
52
|
const aliases = loadAliases();
|
|
53
53
|
|
|
54
54
|
// Parse project path info
|
|
@@ -222,8 +222,8 @@ module.exports = (config) => {
|
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
// GET /api/sessions/:projectName/:sessionId/messages - Get session messages with pagination
|
|
225
|
-
router.get('/:projectName/:sessionId/messages', async (req, res) => {
|
|
226
|
-
|
|
225
|
+
router.get('/:projectName/:sessionId/messages', async (req, res) => {
|
|
226
|
+
try {
|
|
227
227
|
const { projectName, sessionId } = req.params;
|
|
228
228
|
const { page = 1, limit = 20, order = 'desc' } = req.query;
|
|
229
229
|
|
|
@@ -271,86 +271,86 @@ router.get('/:projectName/:sessionId/messages', async (req, res) => {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// Read and parse session file
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
const allMessages = [];
|
|
275
|
+
const metadata = {};
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
const stream = fs.createReadStream(sessionFile, { encoding: 'utf8' });
|
|
278
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
try {
|
|
281
|
+
for await (const line of rl) {
|
|
282
|
+
if (!line.trim()) continue;
|
|
283
|
+
try {
|
|
284
|
+
const json = JSON.parse(line);
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
286
|
+
if (json.type === 'summary' && json.summary) {
|
|
287
|
+
metadata.summary = json.summary;
|
|
288
|
+
}
|
|
289
|
+
if (json.gitBranch) {
|
|
290
|
+
metadata.gitBranch = json.gitBranch;
|
|
291
|
+
}
|
|
292
|
+
if (json.cwd) {
|
|
293
|
+
metadata.cwd = json.cwd;
|
|
294
|
+
}
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
296
|
+
if (json.type === 'user' || json.type === 'assistant') {
|
|
297
|
+
const message = {
|
|
298
|
+
type: json.type,
|
|
299
|
+
content: null,
|
|
300
|
+
timestamp: json.timestamp || null,
|
|
301
|
+
model: json.model || null
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if (json.type === 'user') {
|
|
305
|
+
if (typeof json.message?.content === 'string') {
|
|
306
|
+
message.content = json.message.content;
|
|
307
|
+
} else if (Array.isArray(json.message?.content)) {
|
|
308
|
+
const parts = [];
|
|
309
|
+
for (const item of json.message.content) {
|
|
310
|
+
if (item.type === 'text' && item.text) {
|
|
311
|
+
parts.push(item.text);
|
|
312
|
+
} else if (item.type === 'tool_result') {
|
|
313
|
+
const resultContent = typeof item.content === 'string'
|
|
314
|
+
? item.content
|
|
315
|
+
: JSON.stringify(item.content, null, 2);
|
|
316
|
+
parts.push(`**[工具结果]**\n\`\`\`\n${resultContent}\n\`\`\``);
|
|
317
|
+
} else if (item.type === 'image') {
|
|
318
|
+
parts.push('[图片]');
|
|
319
|
+
}
|
|
319
320
|
}
|
|
321
|
+
message.content = parts.join('\n\n') || '[工具交互]';
|
|
320
322
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
parts.push(`**[思考]**\n${item.thinking}`);
|
|
323
|
+
} else if (json.type === 'assistant') {
|
|
324
|
+
if (Array.isArray(json.message?.content)) {
|
|
325
|
+
const parts = [];
|
|
326
|
+
for (const item of json.message.content) {
|
|
327
|
+
if (item.type === 'text' && item.text) {
|
|
328
|
+
parts.push(item.text);
|
|
329
|
+
} else if (item.type === 'tool_use') {
|
|
330
|
+
const inputStr = JSON.stringify(item.input, null, 2);
|
|
331
|
+
parts.push(`**[调用工具: ${item.name}]**\n\`\`\`json\n${inputStr}\n\`\`\``);
|
|
332
|
+
} else if (item.type === 'thinking' && item.thinking) {
|
|
333
|
+
parts.push(`**[思考]**\n${item.thinking}`);
|
|
334
|
+
}
|
|
334
335
|
}
|
|
336
|
+
message.content = parts.join('\n\n') || '[处理中...]';
|
|
337
|
+
} else if (typeof json.message?.content === 'string') {
|
|
338
|
+
message.content = json.message.content;
|
|
335
339
|
}
|
|
336
|
-
message.content = parts.join('\n\n') || '[处理中...]';
|
|
337
|
-
} else if (typeof json.message?.content === 'string') {
|
|
338
|
-
message.content = json.message.content;
|
|
339
340
|
}
|
|
340
|
-
}
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
if (message.content && message.content !== 'Warmup') {
|
|
343
|
+
allMessages.push(message);
|
|
344
|
+
}
|
|
344
345
|
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
// Skip invalid lines
|
|
345
348
|
}
|
|
346
|
-
} catch (err) {
|
|
347
|
-
// Skip invalid lines
|
|
348
349
|
}
|
|
350
|
+
} finally {
|
|
351
|
+
rl.close();
|
|
352
|
+
stream.destroy();
|
|
349
353
|
}
|
|
350
|
-
} finally {
|
|
351
|
-
rl.close();
|
|
352
|
-
stream.destroy();
|
|
353
|
-
}
|
|
354
354
|
|
|
355
355
|
// Sort messages (desc = newest first)
|
|
356
356
|
if (order === 'desc') {
|