coding-tool-x 3.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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,238 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { getStatistics, getDailyStatistics, getTodayStatistics, getTrendStatistics, getAvailableFilters } = require('../services/statistics-service');
4
+
5
+ /**
6
+ * 获取总体统计数据
7
+ * GET /api/statistics/summary
8
+ */
9
+ router.get('/summary', (req, res) => {
10
+ try {
11
+ const stats = getStatistics();
12
+ res.json(stats);
13
+ } catch (error) {
14
+ console.error('Failed to get statistics:', error);
15
+ res.status(500).json({ error: 'Failed to get statistics' });
16
+ }
17
+ });
18
+
19
+ /**
20
+ * 获取今日统计数据
21
+ * GET /api/statistics/today
22
+ */
23
+ router.get('/today', (req, res) => {
24
+ try {
25
+ const stats = getTodayStatistics();
26
+ res.json(stats);
27
+ } catch (error) {
28
+ console.error('Failed to get today statistics:', error);
29
+ res.status(500).json({ error: 'Failed to get today statistics' });
30
+ }
31
+ });
32
+
33
+ /**
34
+ * 获取指定日期的统计数据
35
+ * GET /api/statistics/daily/:date
36
+ *
37
+ * @param {string} date - 日期,格式:YYYY-MM-DD
38
+ */
39
+ router.get('/daily/:date', (req, res) => {
40
+ try {
41
+ const { date } = req.params;
42
+
43
+ // 验证日期格式
44
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
45
+ return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
46
+ }
47
+
48
+ const stats = getDailyStatistics(date);
49
+ res.json(stats);
50
+ } catch (error) {
51
+ console.error('Failed to get daily statistics:', error);
52
+ res.status(500).json({ error: 'Failed to get daily statistics' });
53
+ }
54
+ });
55
+
56
+ /**
57
+ * 获取最近N天的统计数据
58
+ * GET /api/statistics/recent?days=7
59
+ *
60
+ * @query {number} days - 天数,默认7天
61
+ */
62
+ router.get('/recent', (req, res) => {
63
+ try {
64
+ const days = parseInt(req.query.days) || 7;
65
+
66
+ if (days < 1 || days > 90) {
67
+ return res.status(400).json({ error: 'Days must be between 1 and 90' });
68
+ }
69
+
70
+ const result = [];
71
+ const today = new Date();
72
+
73
+ for (let i = 0; i < days; i++) {
74
+ const date = new Date(today);
75
+ date.setDate(date.getDate() - i);
76
+ const dateStr = date.toISOString().split('T')[0];
77
+ const stats = getDailyStatistics(dateStr);
78
+ result.push({
79
+ date: dateStr,
80
+ ...stats
81
+ });
82
+ }
83
+
84
+ res.json(result);
85
+ } catch (error) {
86
+ console.error('Failed to get recent statistics:', error);
87
+ res.status(500).json({ error: 'Failed to get recent statistics' });
88
+ }
89
+ });
90
+
91
+ /**
92
+ * 获取可用的过滤器选项
93
+ * GET /api/statistics/filters?startDate=&endDate=
94
+ */
95
+ router.get('/filters', (req, res) => {
96
+ try {
97
+ const { startDate, endDate } = req.query;
98
+ if (!startDate || !endDate) {
99
+ return res.status(400).json({ error: 'startDate and endDate are required' });
100
+ }
101
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
102
+ if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) {
103
+ return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
104
+ }
105
+ const result = getAvailableFilters(startDate, endDate);
106
+ res.json(result);
107
+ } catch (error) {
108
+ console.error('Failed to get available filters:', error);
109
+ res.status(500).json({ error: 'Failed to get available filters' });
110
+ }
111
+ });
112
+
113
+ /**
114
+ * 获取趋势统计数据
115
+ * GET /api/statistics/trend
116
+ *
117
+ * @query {string} startDate - YYYY-MM-DD (required)
118
+ * @query {string} endDate - YYYY-MM-DD (required)
119
+ * @query {string} granularity - 'day' | 'hour' (default: 'day')
120
+ * @query {string} groupBy - 'model' | 'channel' | 'toolType' (default: 'model')
121
+ * @query {string} metric - 'tokens' | 'cost' | 'requests' (default: 'tokens')
122
+ * @query {string} filterToolType - optional filter
123
+ * @query {string} filterChannel - optional filter
124
+ * @query {string} filterModel - optional filter
125
+ */
126
+ router.get('/trend', async (req, res) => {
127
+ try {
128
+ const {
129
+ startDate, endDate, granularity = 'day', step = 1, groupBy = 'model', metric = 'tokens',
130
+ filterToolType, filterChannel, filterModel
131
+ } = req.query;
132
+
133
+ if (!startDate || !endDate) {
134
+ return res.status(400).json({ error: 'startDate and endDate are required' });
135
+ }
136
+
137
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
138
+ if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) {
139
+ return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
140
+ }
141
+
142
+ const start = new Date(startDate);
143
+ const end = new Date(endDate);
144
+ const diffDays = Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1;
145
+
146
+ if (diffDays < 1) {
147
+ return res.status(400).json({ error: 'endDate must be >= startDate' });
148
+ }
149
+
150
+ if (granularity === 'hour' && diffDays > 7) {
151
+ return res.status(400).json({ error: 'Hour granularity is limited to 7 days' });
152
+ }
153
+
154
+ if (diffDays > 90) {
155
+ return res.status(400).json({ error: 'Date range cannot exceed 90 days' });
156
+ }
157
+
158
+ const filters = {
159
+ toolType: filterToolType || null,
160
+ channel: filterChannel || null,
161
+ model: filterModel || null
162
+ };
163
+ const result = await getTrendStatistics({ startDate, endDate, granularity, step, groupBy, metric, filters });
164
+ res.json({ success: true, ...result });
165
+ } catch (error) {
166
+ console.error('Failed to get trend statistics:', error);
167
+ res.status(500).json({ error: 'Failed to get trend statistics' });
168
+ }
169
+ });
170
+
171
+ /**
172
+ * 导出趋势统计数据
173
+ * GET /api/statistics/trend/export
174
+ *
175
+ * @query {string} startDate - YYYY-MM-DD (required)
176
+ * @query {string} endDate - YYYY-MM-DD (required)
177
+ * @query {string} granularity - 'day' | 'hour' (default: 'day')
178
+ * @query {string} groupBy - 'model' | 'channel' | 'toolType' (default: 'model')
179
+ * @query {string} metric - 'tokens' | 'cost' | 'requests' (default: 'tokens')
180
+ * @query {string} format - 'csv' | 'json' (default: 'csv')
181
+ */
182
+ router.get('/trend/export', async (req, res) => {
183
+ try {
184
+ const { startDate, endDate, granularity = 'day', step = 1, groupBy = 'model', metric = 'tokens', format = 'csv' } = req.query;
185
+
186
+ if (!startDate || !endDate) {
187
+ return res.status(400).json({ error: 'startDate and endDate are required' });
188
+ }
189
+
190
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
191
+ if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) {
192
+ return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
193
+ }
194
+
195
+ const start = new Date(startDate);
196
+ const end = new Date(endDate);
197
+ const diffDays = Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1;
198
+
199
+ if (diffDays < 1) {
200
+ return res.status(400).json({ error: 'endDate must be >= startDate' });
201
+ }
202
+
203
+ if (granularity === 'hour' && diffDays > 7) {
204
+ return res.status(400).json({ error: 'Hour granularity is limited to 7 days' });
205
+ }
206
+
207
+ if (diffDays > 90) {
208
+ return res.status(400).json({ error: 'Date range cannot exceed 90 days' });
209
+ }
210
+
211
+ const result = await getTrendStatistics({ startDate, endDate, granularity, step, groupBy, metric });
212
+
213
+ const filename = `analytics-${startDate}-${endDate}-${groupBy}-${metric}.${format}`;
214
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
215
+
216
+ if (format === 'json') {
217
+ res.setHeader('Content-Type', 'application/json');
218
+ return res.json(result);
219
+ }
220
+
221
+ // CSV format
222
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
223
+ const seriesNames = result.series.map(s => s.name);
224
+ const header = ['Date/Time', ...seriesNames, 'Total'].join(',');
225
+ const rows = result.labels.map((label, i) => {
226
+ const values = result.series.map(s => s.data[i] || 0);
227
+ const total = values.reduce((a, b) => a + b, 0);
228
+ return [label, ...values, total].join(',');
229
+ });
230
+
231
+ res.send([header, ...rows].join('\n'));
232
+ } catch (error) {
233
+ console.error('Failed to export trend statistics:', error);
234
+ res.status(500).json({ error: 'Failed to export trend statistics' });
235
+ }
236
+ });
237
+
238
+ module.exports = router;
@@ -0,0 +1,64 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ loadUIConfig,
5
+ saveUIConfig,
6
+ updateUIConfig,
7
+ updateNestedUIConfig
8
+ } = require('../services/ui-config');
9
+
10
+ // Get all UI config
11
+ router.get('/', (req, res) => {
12
+ try {
13
+ const config = loadUIConfig();
14
+ res.json({ success: true, config });
15
+ } catch (error) {
16
+ console.error('Error getting UI config:', error);
17
+ res.status(500).json({ error: error.message });
18
+ }
19
+ });
20
+
21
+ // Update entire UI config
22
+ router.post('/', (req, res) => {
23
+ try {
24
+ const { config } = req.body;
25
+ if (!config) {
26
+ return res.status(400).json({ error: 'Missing config' });
27
+ }
28
+ saveUIConfig(config);
29
+ res.json({ success: true, config });
30
+ } catch (error) {
31
+ console.error('Error saving UI config:', error);
32
+ res.status(500).json({ error: error.message });
33
+ }
34
+ });
35
+
36
+ // Update specific config key
37
+ router.put('/:key', (req, res) => {
38
+ try {
39
+ const { key } = req.params;
40
+ const { value } = req.body;
41
+
42
+ const config = updateUIConfig(key, value);
43
+ res.json({ success: true, config });
44
+ } catch (error) {
45
+ console.error('Error updating UI config:', error);
46
+ res.status(500).json({ error: error.message });
47
+ }
48
+ });
49
+
50
+ // Update nested config
51
+ router.put('/:parentKey/:childKey', (req, res) => {
52
+ try {
53
+ const { parentKey, childKey } = req.params;
54
+ const { value } = req.body;
55
+
56
+ const config = updateNestedUIConfig(parentKey, childKey, value);
57
+ res.json({ success: true, config });
58
+ } catch (error) {
59
+ console.error('Error updating nested UI config:', error);
60
+ res.status(500).json({ error: error.message });
61
+ }
62
+ });
63
+
64
+ module.exports = router;