@adversity/coding-tool-x 2.2.0

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 (125) hide show
  1. package/CHANGELOG.md +333 -0
  2. package/LICENSE +21 -0
  3. package/README.md +404 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
  6. package/dist/web/assets/index-aL3cKxSK.css +41 -0
  7. package/dist/web/favicon.ico +0 -0
  8. package/dist/web/index.html +14 -0
  9. package/dist/web/logo.png +0 -0
  10. package/docs/CHANGELOG.md +582 -0
  11. package/docs/DIRECTORY_MIGRATION.md +112 -0
  12. package/docs/PROJECT_STRUCTURE.md +396 -0
  13. package/docs/bannel.png +0 -0
  14. package/docs/home.png +0 -0
  15. package/docs/logo.png +0 -0
  16. package/docs/multi-channel-load-balancing.md +249 -0
  17. package/package.json +73 -0
  18. package/src/commands/channels.js +504 -0
  19. package/src/commands/cli-type.js +99 -0
  20. package/src/commands/daemon.js +286 -0
  21. package/src/commands/doctor.js +332 -0
  22. package/src/commands/list.js +222 -0
  23. package/src/commands/logs.js +259 -0
  24. package/src/commands/port-config.js +115 -0
  25. package/src/commands/proxy-control.js +258 -0
  26. package/src/commands/proxy.js +152 -0
  27. package/src/commands/resume.js +137 -0
  28. package/src/commands/search.js +190 -0
  29. package/src/commands/stats.js +224 -0
  30. package/src/commands/switch.js +48 -0
  31. package/src/commands/toggle-proxy.js +222 -0
  32. package/src/commands/ui.js +92 -0
  33. package/src/commands/workspace.js +454 -0
  34. package/src/config/default.js +40 -0
  35. package/src/config/loader.js +75 -0
  36. package/src/config/paths.js +121 -0
  37. package/src/index.js +373 -0
  38. package/src/reset-config.js +92 -0
  39. package/src/server/api/agents.js +248 -0
  40. package/src/server/api/aliases.js +36 -0
  41. package/src/server/api/channels.js +258 -0
  42. package/src/server/api/claude-hooks.js +480 -0
  43. package/src/server/api/codex-channels.js +312 -0
  44. package/src/server/api/codex-projects.js +91 -0
  45. package/src/server/api/codex-proxy.js +182 -0
  46. package/src/server/api/codex-sessions.js +491 -0
  47. package/src/server/api/codex-statistics.js +57 -0
  48. package/src/server/api/commands.js +245 -0
  49. package/src/server/api/config-templates.js +182 -0
  50. package/src/server/api/config.js +147 -0
  51. package/src/server/api/convert.js +127 -0
  52. package/src/server/api/dashboard.js +125 -0
  53. package/src/server/api/env.js +144 -0
  54. package/src/server/api/favorites.js +77 -0
  55. package/src/server/api/gemini-channels.js +261 -0
  56. package/src/server/api/gemini-projects.js +91 -0
  57. package/src/server/api/gemini-proxy.js +160 -0
  58. package/src/server/api/gemini-sessions.js +397 -0
  59. package/src/server/api/gemini-statistics.js +57 -0
  60. package/src/server/api/health-check.js +118 -0
  61. package/src/server/api/mcp.js +336 -0
  62. package/src/server/api/pm2-autostart.js +269 -0
  63. package/src/server/api/projects.js +124 -0
  64. package/src/server/api/prompts.js +279 -0
  65. package/src/server/api/proxy.js +235 -0
  66. package/src/server/api/rules.js +271 -0
  67. package/src/server/api/sessions.js +595 -0
  68. package/src/server/api/settings.js +61 -0
  69. package/src/server/api/skills.js +305 -0
  70. package/src/server/api/statistics.js +91 -0
  71. package/src/server/api/terminal.js +202 -0
  72. package/src/server/api/ui-config.js +64 -0
  73. package/src/server/api/workspaces.js +407 -0
  74. package/src/server/codex-proxy-server.js +538 -0
  75. package/src/server/dev-server.js +26 -0
  76. package/src/server/gemini-proxy-server.js +518 -0
  77. package/src/server/index.js +305 -0
  78. package/src/server/proxy-server.js +469 -0
  79. package/src/server/services/agents-service.js +354 -0
  80. package/src/server/services/alias.js +71 -0
  81. package/src/server/services/channel-health.js +234 -0
  82. package/src/server/services/channel-scheduler.js +234 -0
  83. package/src/server/services/channels.js +347 -0
  84. package/src/server/services/codex-channels.js +625 -0
  85. package/src/server/services/codex-config.js +90 -0
  86. package/src/server/services/codex-parser.js +322 -0
  87. package/src/server/services/codex-sessions.js +665 -0
  88. package/src/server/services/codex-settings-manager.js +397 -0
  89. package/src/server/services/codex-speed-test-template.json +24 -0
  90. package/src/server/services/codex-statistics-service.js +255 -0
  91. package/src/server/services/commands-service.js +360 -0
  92. package/src/server/services/config-templates-service.js +732 -0
  93. package/src/server/services/env-checker.js +307 -0
  94. package/src/server/services/env-manager.js +300 -0
  95. package/src/server/services/favorites.js +163 -0
  96. package/src/server/services/gemini-channels.js +333 -0
  97. package/src/server/services/gemini-config.js +73 -0
  98. package/src/server/services/gemini-sessions.js +689 -0
  99. package/src/server/services/gemini-settings-manager.js +263 -0
  100. package/src/server/services/gemini-statistics-service.js +253 -0
  101. package/src/server/services/health-check.js +399 -0
  102. package/src/server/services/mcp-service.js +1188 -0
  103. package/src/server/services/prompts-service.js +492 -0
  104. package/src/server/services/proxy-runtime.js +79 -0
  105. package/src/server/services/pty-manager.js +435 -0
  106. package/src/server/services/rules-service.js +401 -0
  107. package/src/server/services/session-cache.js +127 -0
  108. package/src/server/services/session-converter.js +577 -0
  109. package/src/server/services/sessions.js +757 -0
  110. package/src/server/services/settings-manager.js +163 -0
  111. package/src/server/services/skill-service.js +965 -0
  112. package/src/server/services/speed-test.js +545 -0
  113. package/src/server/services/statistics-service.js +386 -0
  114. package/src/server/services/terminal-commands.js +155 -0
  115. package/src/server/services/terminal-config.js +140 -0
  116. package/src/server/services/terminal-detector.js +306 -0
  117. package/src/server/services/ui-config.js +130 -0
  118. package/src/server/services/workspace-service.js +662 -0
  119. package/src/server/utils/pricing.js +41 -0
  120. package/src/server/websocket-server.js +557 -0
  121. package/src/ui/menu.js +129 -0
  122. package/src/ui/prompts.js +100 -0
  123. package/src/utils/format.js +43 -0
  124. package/src/utils/port-helper.js +94 -0
  125. package/src/utils/session.js +239 -0
@@ -0,0 +1,322 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * 读取 JSONL 文件
5
+ * @param {string} filePath - JSONL 文件路径
6
+ * @returns {Array} JSON 对象数组
7
+ */
8
+ function readJSONL(filePath) {
9
+ if (!fs.existsSync(filePath)) {
10
+ return [];
11
+ }
12
+
13
+ try {
14
+ const content = fs.readFileSync(filePath, 'utf8');
15
+ const lines = content.trim().split('\n').filter(line => line);
16
+
17
+ return lines.map((line, index) => {
18
+ try {
19
+ return JSON.parse(line);
20
+ } catch (err) {
21
+ console.error(`[Codex] Failed to parse line ${index + 1} in ${filePath}:`, err.message);
22
+ return null;
23
+ }
24
+ }).filter(Boolean);
25
+ } catch (err) {
26
+ console.error('[Codex] Failed to read JSONL file:', filePath, err);
27
+ return [];
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 提取会话元数据
33
+ * @param {Array} lines - JSONL 行数组
34
+ * @returns {Object|null} 会话元数据
35
+ */
36
+ function extractSessionMeta(lines) {
37
+ const metaLine = lines.find(line => line.type === 'session_meta');
38
+
39
+ if (!metaLine || !metaLine.payload) {
40
+ return null;
41
+ }
42
+
43
+ const payload = metaLine.payload;
44
+
45
+ return {
46
+ sessionId: payload.id,
47
+ timestamp: payload.timestamp,
48
+ cwd: payload.cwd,
49
+ cliVersion: payload.cli_version,
50
+ provider: payload.model_provider,
51
+ git: payload.git ? {
52
+ branch: payload.git.branch,
53
+ commitHash: payload.git.commit_hash,
54
+ repositoryUrl: payload.git.repository_url
55
+ } : null
56
+ };
57
+ }
58
+
59
+ /**
60
+ * 提取对话消息
61
+ * @param {Array} lines - JSONL 行数组
62
+ * @returns {Array} 消息数组
63
+ */
64
+ function extractMessages(lines) {
65
+ const messages = [];
66
+
67
+ lines.forEach(line => {
68
+ if (line.type !== 'response_item') return;
69
+
70
+ const payload = line.payload;
71
+
72
+ // 用户/助手消息
73
+ if (payload.type === 'message') {
74
+ const contentParts = payload.content || [];
75
+ const text = contentParts
76
+ .map(c => c.text || c.input_text || '')
77
+ .join('\n')
78
+ .trim();
79
+
80
+ if (text) {
81
+ messages.push({
82
+ role: payload.role,
83
+ content: text,
84
+ timestamp: line.timestamp
85
+ });
86
+ }
87
+ }
88
+
89
+ // 工具调用
90
+ if (payload.type === 'function_call') {
91
+ // 解析 arguments 字段(它是一个序列化的 JSON 字符串)
92
+ let parsedArguments = payload.arguments;
93
+ try {
94
+ parsedArguments = JSON.parse(payload.arguments);
95
+ } catch (err) {
96
+ // 如果解析失败,保持原样
97
+ }
98
+
99
+ messages.push({
100
+ role: 'tool_call',
101
+ name: payload.name,
102
+ arguments: parsedArguments,
103
+ callId: payload.call_id,
104
+ timestamp: line.timestamp
105
+ });
106
+ }
107
+
108
+ // 工具输出
109
+ if (payload.type === 'function_call_output') {
110
+ // 解析 output 字段(它是一个序列化的 JSON 字符串)
111
+ let parsedOutput = payload.output;
112
+ try {
113
+ parsedOutput = JSON.parse(payload.output);
114
+ } catch (err) {
115
+ // 如果解析失败,保持原样(output 可能是普通文本如 "Exit code: 0")
116
+ }
117
+
118
+ messages.push({
119
+ role: 'tool_output',
120
+ callId: payload.call_id,
121
+ output: parsedOutput,
122
+ timestamp: line.timestamp
123
+ });
124
+ }
125
+
126
+ // 推理内容
127
+ if (payload.type === 'reasoning') {
128
+ const summary = payload.summary || [];
129
+ const text = summary
130
+ .map(s => s.text || '')
131
+ .join('\n')
132
+ .trim();
133
+
134
+ if (text) {
135
+ messages.push({
136
+ role: 'reasoning',
137
+ content: text,
138
+ timestamp: line.timestamp
139
+ });
140
+ }
141
+ }
142
+ });
143
+
144
+ return messages;
145
+ }
146
+
147
+ /**
148
+ * 提取 Token 统计
149
+ * @param {Array} lines - JSONL 行数组
150
+ * @returns {Object|null} Token 统计
151
+ */
152
+ function extractTokenUsage(lines) {
153
+ // 找到最后一个有效的 token_count 事件(info 和 total_token_usage 都存在)
154
+ const tokenEvents = lines.filter(line =>
155
+ line.type === 'event_msg' &&
156
+ line.payload?.type === 'token_count' &&
157
+ line.payload.info &&
158
+ line.payload.info.total_token_usage
159
+ );
160
+
161
+ if (tokenEvents.length === 0) {
162
+ return null;
163
+ }
164
+
165
+ const lastEvent = tokenEvents[tokenEvents.length - 1];
166
+ const usage = lastEvent.payload.info.total_token_usage;
167
+
168
+ return {
169
+ input: usage.input_tokens || 0,
170
+ output: usage.output_tokens || 0,
171
+ cacheRead: usage.cached_input_tokens || 0,
172
+ cacheCreation: usage.cache_creation_input_tokens || 0,
173
+ reasoning: usage.reasoning_output_tokens || 0,
174
+ total: usage.total_tokens || 0
175
+ };
176
+ }
177
+
178
+ /**
179
+ * 轻量级解析会话元数据(不解析完整消息,用于列表展示)
180
+ * @param {string} filePath - JSONL 文件路径
181
+ * @returns {Object} 会话元数据对象
182
+ */
183
+ function parseSessionMeta(filePath) {
184
+ try {
185
+ const content = fs.readFileSync(filePath, 'utf8');
186
+ const lines = content.split('\n').filter(line => line.trim());
187
+
188
+ if (lines.length === 0) {
189
+ return null;
190
+ }
191
+
192
+ let meta = null;
193
+ let tokens = null;
194
+ let messageCount = 0;
195
+ let firstUserMessage = null;
196
+
197
+ // 只读取前面的行,获取元数据和第一条消息
198
+ for (let i = 0; i < Math.min(lines.length, 50); i++) {
199
+ try {
200
+ const json = JSON.parse(lines[i]);
201
+
202
+ // 提取 session_meta
203
+ if (!meta && json.type === 'session_meta' && json.payload) {
204
+ const payload = json.payload;
205
+ meta = {
206
+ sessionId: payload.id,
207
+ timestamp: payload.timestamp,
208
+ cwd: payload.cwd,
209
+ cliVersion: payload.cli_version,
210
+ provider: payload.model_provider,
211
+ git: payload.git ? {
212
+ branch: payload.git.branch,
213
+ commitHash: payload.git.commit_hash,
214
+ repositoryUrl: payload.git.repository_url
215
+ } : null
216
+ };
217
+ }
218
+
219
+ // 获取第一条用户消息作为预览
220
+ if (!firstUserMessage && json.type === 'response_item' && json.payload?.type === 'message' && json.payload?.role === 'user') {
221
+ const contentParts = json.payload.content || [];
222
+ const text = contentParts
223
+ .map(c => c.text || c.input_text || '')
224
+ .join('\n')
225
+ .trim();
226
+ // 跳过环境上下文、Warmup 等非真实用户消息
227
+ if (text &&
228
+ text !== 'Warmup' &&
229
+ !text.startsWith('<environment_context>')) {
230
+ firstUserMessage = text.substring(0, 100);
231
+ }
232
+ }
233
+ } catch (err) {
234
+ // 跳过无效行
235
+ }
236
+ }
237
+
238
+ // 快速统计消息数量(只统计 response_item)
239
+ for (const line of lines) {
240
+ if (line.includes('"type":"response_item"')) {
241
+ messageCount++;
242
+ }
243
+ }
244
+
245
+ // 从最后几行提取 token 统计
246
+ for (let i = lines.length - 1; i >= Math.max(0, lines.length - 20); i--) {
247
+ try {
248
+ const json = JSON.parse(lines[i]);
249
+ if (json.type === 'event_msg' &&
250
+ json.payload?.type === 'token_count' &&
251
+ json.payload.info &&
252
+ json.payload.info.total_token_usage) {
253
+ const usage = json.payload.info.total_token_usage;
254
+ tokens = {
255
+ input: usage.input_tokens || 0,
256
+ output: usage.output_tokens || 0,
257
+ cacheRead: usage.cached_input_tokens || 0,
258
+ cacheCreation: usage.cache_creation_input_tokens || 0,
259
+ reasoning: usage.reasoning_output_tokens || 0,
260
+ total: usage.total_tokens || 0
261
+ };
262
+ break;
263
+ }
264
+ } catch (err) {
265
+ // 跳过无效行
266
+ }
267
+ }
268
+
269
+ if (!meta) {
270
+ return null;
271
+ }
272
+
273
+ return {
274
+ filePath,
275
+ meta,
276
+ tokens,
277
+ messageCount,
278
+ preview: firstUserMessage || ''
279
+ };
280
+ } catch (err) {
281
+ console.error(`[Codex Parser] Failed to parse session meta for ${filePath}:`, err.message);
282
+ return null;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * 解析完整会话(包含所有消息,用于查看详情)
288
+ * @param {string} filePath - JSONL 文件路径
289
+ * @returns {Object} 会话对象
290
+ */
291
+ function parseSession(filePath) {
292
+ const lines = readJSONL(filePath);
293
+
294
+ if (lines.length === 0) {
295
+ return null;
296
+ }
297
+
298
+ const meta = extractSessionMeta(lines);
299
+ const messages = extractMessages(lines);
300
+ const tokens = extractTokenUsage(lines);
301
+
302
+ if (!meta) {
303
+ return null;
304
+ }
305
+
306
+ return {
307
+ filePath,
308
+ meta,
309
+ messages,
310
+ tokens,
311
+ messageCount: messages.filter(m => m.role === 'user' || m.role === 'assistant').length
312
+ };
313
+ }
314
+
315
+ module.exports = {
316
+ readJSONL,
317
+ extractSessionMeta,
318
+ extractMessages,
319
+ extractTokenUsage,
320
+ parseSession,
321
+ parseSessionMeta
322
+ };