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,263 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ // Gemini 配置文件路径
6
+ function getEnvPath() {
7
+ return path.join(os.homedir(), '.gemini', '.env');
8
+ }
9
+
10
+ function getSettingsPath() {
11
+ return path.join(os.homedir(), '.gemini', 'settings.json');
12
+ }
13
+
14
+ // 备份文件路径
15
+ function getEnvBackupPath() {
16
+ return path.join(os.homedir(), '.gemini', '.env.cc-tool-backup');
17
+ }
18
+
19
+ function getSettingsBackupPath() {
20
+ return path.join(os.homedir(), '.gemini', 'settings.json.cc-tool-backup');
21
+ }
22
+
23
+ // 检查配置文件是否存在
24
+ function configExists() {
25
+ return fs.existsSync(getEnvPath());
26
+ }
27
+
28
+ function settingsExists() {
29
+ return fs.existsSync(getSettingsPath());
30
+ }
31
+
32
+ // 检查是否已经有备份
33
+ function hasBackup() {
34
+ return fs.existsSync(getEnvBackupPath()) || fs.existsSync(getSettingsBackupPath());
35
+ }
36
+
37
+ // 读取 .env
38
+ function readEnv() {
39
+ try {
40
+ const content = fs.readFileSync(getEnvPath(), 'utf8');
41
+ const env = {};
42
+
43
+ content.split('\n').forEach(line => {
44
+ const trimmed = line.trim();
45
+ if (!trimmed || trimmed.startsWith('#')) return;
46
+
47
+ const match = trimmed.match(/^([^=]+)=(.*)$/);
48
+ if (match) {
49
+ env[match[1].trim()] = match[2].trim();
50
+ }
51
+ });
52
+
53
+ return env;
54
+ } catch (err) {
55
+ throw new Error('Failed to read .env: ' + err.message);
56
+ }
57
+ }
58
+
59
+ // 将环境对象转换为 .env 字符串
60
+ function envToString(env) {
61
+ let content = '';
62
+ for (const [key, value] of Object.entries(env)) {
63
+ content += `${key}=${value}\n`;
64
+ }
65
+ return content;
66
+ }
67
+
68
+ // 写入 .env
69
+ function writeEnv(env) {
70
+ try {
71
+ const content = envToString(env);
72
+ fs.writeFileSync(getEnvPath(), content, 'utf8');
73
+
74
+ // 设置文件权限为 600 (仅所有者可读写)
75
+ if (process.platform !== 'win32') {
76
+ fs.chmodSync(getEnvPath(), 0o600);
77
+ }
78
+ } catch (err) {
79
+ throw new Error('Failed to write .env: ' + err.message);
80
+ }
81
+ }
82
+
83
+ // 读取 settings.json
84
+ function readSettings() {
85
+ try {
86
+ if (!settingsExists()) {
87
+ return {};
88
+ }
89
+ const content = fs.readFileSync(getSettingsPath(), 'utf8');
90
+ return JSON.parse(content);
91
+ } catch (err) {
92
+ throw new Error('Failed to read settings.json: ' + err.message);
93
+ }
94
+ }
95
+
96
+ // 写入 settings.json
97
+ function writeSettings(settings) {
98
+ try {
99
+ const content = JSON.stringify(settings, null, 2);
100
+ fs.writeFileSync(getSettingsPath(), content, 'utf8');
101
+ } catch (err) {
102
+ throw new Error('Failed to write settings.json: ' + err.message);
103
+ }
104
+ }
105
+
106
+ // 备份当前配置
107
+ function backupSettings() {
108
+ try {
109
+ if (!configExists()) {
110
+ throw new Error('.env not found');
111
+ }
112
+
113
+ // 如果已经有备份,不覆盖
114
+ if (hasBackup()) {
115
+ console.log('Backup already exists, skipping backup');
116
+ return { success: true, alreadyExists: true };
117
+ }
118
+
119
+ // 备份 .env
120
+ const envContent = fs.readFileSync(getEnvPath(), 'utf8');
121
+ fs.writeFileSync(getEnvBackupPath(), envContent, 'utf8');
122
+
123
+ // 设置备份文件权限为 600
124
+ if (process.platform !== 'win32') {
125
+ fs.chmodSync(getEnvBackupPath(), 0o600);
126
+ }
127
+
128
+ // 备份 settings.json (如果存在)
129
+ if (settingsExists()) {
130
+ const settingsContent = fs.readFileSync(getSettingsPath(), 'utf8');
131
+ fs.writeFileSync(getSettingsBackupPath(), settingsContent, 'utf8');
132
+ }
133
+
134
+ console.log('Gemini settings backed up');
135
+ return { success: true, alreadyExists: false };
136
+ } catch (err) {
137
+ throw new Error('Failed to backup settings: ' + err.message);
138
+ }
139
+ }
140
+
141
+ // 恢复配置
142
+ function restoreSettings() {
143
+ try {
144
+ if (!hasBackup()) {
145
+ throw new Error('No backup found');
146
+ }
147
+
148
+ // 恢复 .env
149
+ if (fs.existsSync(getEnvBackupPath())) {
150
+ const content = fs.readFileSync(getEnvBackupPath(), 'utf8');
151
+ fs.writeFileSync(getEnvPath(), content, 'utf8');
152
+ fs.unlinkSync(getEnvBackupPath());
153
+
154
+ // 设置文件权限为 600
155
+ if (process.platform !== 'win32') {
156
+ fs.chmodSync(getEnvPath(), 0o600);
157
+ }
158
+ }
159
+
160
+ // 恢复 settings.json
161
+ if (fs.existsSync(getSettingsBackupPath())) {
162
+ const content = fs.readFileSync(getSettingsBackupPath(), 'utf8');
163
+ fs.writeFileSync(getSettingsPath(), content, 'utf8');
164
+ fs.unlinkSync(getSettingsBackupPath());
165
+ }
166
+
167
+ console.log('Gemini settings restored from backup');
168
+ return { success: true };
169
+ } catch (err) {
170
+ throw new Error('Failed to restore settings: ' + err.message);
171
+ }
172
+ }
173
+
174
+ // 设置代理配置
175
+ function setProxyConfig(proxyPort) {
176
+ try {
177
+ // 先备份
178
+ backupSettings();
179
+
180
+ // 读取当前配置
181
+ const env = configExists() ? readEnv() : {};
182
+
183
+ // 设置代理 URL
184
+ env.GOOGLE_GEMINI_BASE_URL = `http://127.0.0.1:${proxyPort}`;
185
+ env.GEMINI_API_KEY = 'PROXY_KEY';
186
+ // 保留或设置默认模型
187
+ if (!env.GEMINI_MODEL) {
188
+ env.GEMINI_MODEL = 'gemini-2.5-pro';
189
+ }
190
+
191
+ // 写入 .env
192
+ writeEnv(env);
193
+
194
+ // 确保 settings.json 存在并配置正确的认证模式
195
+ const settings = settingsExists() ? readSettings() : {};
196
+ settings.security = settings.security || {};
197
+ settings.security.auth = settings.security.auth || {};
198
+ settings.security.auth.selectedType = 'gemini-api-key';
199
+
200
+ writeSettings(settings);
201
+
202
+ console.log(`Gemini settings updated to use proxy on port ${proxyPort}`);
203
+ return { success: true, port: proxyPort };
204
+ } catch (err) {
205
+ throw new Error('Failed to set proxy config: ' + err.message);
206
+ }
207
+ }
208
+
209
+ // 检查当前是否是代理配置
210
+ function isProxyConfig() {
211
+ try {
212
+ if (!configExists()) {
213
+ return false;
214
+ }
215
+
216
+ const env = readEnv();
217
+
218
+ // 检查 GOOGLE_GEMINI_BASE_URL 是否指向本地代理
219
+ const baseUrl = env.GOOGLE_GEMINI_BASE_URL || '';
220
+ if (baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost')) {
221
+ return true;
222
+ }
223
+
224
+ return false;
225
+ } catch (err) {
226
+ return false;
227
+ }
228
+ }
229
+
230
+ // 获取当前代理端口(如果是代理配置)
231
+ function getCurrentProxyPort() {
232
+ try {
233
+ if (!isProxyConfig()) {
234
+ return null;
235
+ }
236
+
237
+ const env = readEnv();
238
+ const baseUrl = env.GOOGLE_GEMINI_BASE_URL || '';
239
+ const match = baseUrl.match(/:(\d+)/);
240
+ return match ? parseInt(match[1]) : null;
241
+ } catch (err) {
242
+ return null;
243
+ }
244
+ }
245
+
246
+ module.exports = {
247
+ getEnvPath,
248
+ getSettingsPath,
249
+ getEnvBackupPath,
250
+ getSettingsBackupPath,
251
+ configExists,
252
+ settingsExists,
253
+ hasBackup,
254
+ readEnv,
255
+ writeEnv,
256
+ readSettings,
257
+ writeSettings,
258
+ backupSettings,
259
+ restoreSettings,
260
+ setProxyConfig,
261
+ isProxyConfig,
262
+ getCurrentProxyPort
263
+ };
@@ -0,0 +1,157 @@
1
+ const {
2
+ recordRequest: recordSharedRequest,
3
+ getStatistics: getSharedStatistics,
4
+ getDailyStatistics: getSharedDailyStatistics,
5
+ getTodayStatistics: getSharedTodayStatistics
6
+ } = require('./statistics-service');
7
+
8
+ const TOOL_TYPE = 'gemini';
9
+
10
+ function toNumber(value) {
11
+ const num = Number(value);
12
+ return Number.isFinite(num) ? num : 0;
13
+ }
14
+
15
+ function normalizeToolTokens(tokens = {}) {
16
+ const input = toNumber(tokens.input);
17
+ const output = toNumber(tokens.output);
18
+ const cached = toNumber(tokens.cached);
19
+ const cacheCreation = toNumber(tokens.cacheCreation);
20
+ const cacheRead = toNumber(tokens.cacheRead || cached);
21
+ const total = toNumber(tokens.total) || (input + output);
22
+
23
+ return {
24
+ input,
25
+ output,
26
+ cached,
27
+ cacheCreation,
28
+ cacheRead,
29
+ total
30
+ };
31
+ }
32
+
33
+ function toLegacyEntryShape(entry = {}, includeName = false) {
34
+ const normalized = normalizeToolTokens(entry.tokens || {});
35
+ const result = {
36
+ requests: toNumber(entry.requests),
37
+ tokens: {
38
+ input: normalized.input,
39
+ output: normalized.output,
40
+ cached: normalized.cached,
41
+ total: normalized.total
42
+ },
43
+ cost: toNumber(entry.cost)
44
+ };
45
+
46
+ if (includeName) {
47
+ result.name = entry.name || '';
48
+ if (entry.firstUsed) result.firstUsed = entry.firstUsed;
49
+ if (entry.lastUsed) result.lastUsed = entry.lastUsed;
50
+ }
51
+
52
+ return result;
53
+ }
54
+
55
+ function pickToolScope(sharedStats = {}) {
56
+ const byToolType = sharedStats.byToolType || {};
57
+ const toolScope = byToolType[TOOL_TYPE] || {};
58
+
59
+ let byChannel = {};
60
+ let byModel = {};
61
+
62
+ if (toolScope.channels && typeof toolScope.channels === 'object') {
63
+ byChannel = toolScope.channels;
64
+ } else {
65
+ byChannel = Object.fromEntries(
66
+ Object.entries(sharedStats.byChannel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE)
67
+ );
68
+ }
69
+
70
+ if (toolScope.models && typeof toolScope.models === 'object') {
71
+ byModel = toolScope.models;
72
+ } else {
73
+ byModel = Object.fromEntries(
74
+ Object.entries(sharedStats.byModel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE)
75
+ );
76
+ }
77
+
78
+ return { toolScope, byChannel, byModel };
79
+ }
80
+
81
+ function buildSummaryStatistics(sharedStats = {}) {
82
+ const { toolScope, byChannel, byModel } = pickToolScope(sharedStats);
83
+ const normalized = toLegacyEntryShape(toolScope);
84
+
85
+ return {
86
+ version: '1.0',
87
+ lastUpdated: sharedStats.lastUpdated || new Date().toISOString(),
88
+ global: {
89
+ totalRequests: normalized.requests,
90
+ totalTokens: normalized.tokens.total,
91
+ totalCost: normalized.cost
92
+ },
93
+ byChannel: Object.fromEntries(
94
+ Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])
95
+ ),
96
+ byModel: Object.fromEntries(
97
+ Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)])
98
+ )
99
+ };
100
+ }
101
+
102
+ function buildDailyStatistics(sharedDaily = {}, fallbackDate) {
103
+ const { byToolType = {} } = sharedDaily;
104
+ const toolScope = byToolType[TOOL_TYPE] || {};
105
+ const normalized = toLegacyEntryShape(toolScope);
106
+ const byChannel = toolScope.channels || {};
107
+ const byModel = toolScope.models || {};
108
+
109
+ return {
110
+ date: sharedDaily.date || fallbackDate,
111
+ summary: {
112
+ requests: normalized.requests,
113
+ tokens: normalized.tokens.total,
114
+ cost: normalized.cost
115
+ },
116
+ byChannel: Object.fromEntries(
117
+ Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])
118
+ ),
119
+ byModel: Object.fromEntries(
120
+ Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)])
121
+ )
122
+ };
123
+ }
124
+
125
+ function recordRequest(requestData = {}) {
126
+ const normalizedTokens = normalizeToolTokens(requestData.tokens || {});
127
+ return recordSharedRequest({
128
+ ...requestData,
129
+ toolType: TOOL_TYPE,
130
+ tokens: {
131
+ input: normalizedTokens.input,
132
+ output: normalizedTokens.output,
133
+ cacheCreation: normalizedTokens.cacheCreation,
134
+ cacheRead: normalizedTokens.cacheRead,
135
+ total: normalizedTokens.total
136
+ }
137
+ });
138
+ }
139
+
140
+ function getStatistics() {
141
+ return buildSummaryStatistics(getSharedStatistics());
142
+ }
143
+
144
+ function getDailyStatistics(date) {
145
+ return buildDailyStatistics(getSharedDailyStatistics(date), date);
146
+ }
147
+
148
+ function getTodayStatistics() {
149
+ return buildDailyStatistics(getSharedTodayStatistics());
150
+ }
151
+
152
+ module.exports = {
153
+ recordRequest,
154
+ getStatistics,
155
+ getDailyStatistics,
156
+ getTodayStatistics
157
+ };
@@ -0,0 +1,85 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * 健康检查:确保项目的 .claude/sessions 目录存在
6
+ * @param {string} projectPath - 项目路径
7
+ * @returns {Object} 检查结果
8
+ */
9
+ function ensureProjectClaudeDir(projectPath) {
10
+ try {
11
+ const claudeDir = path.join(projectPath, '.claude');
12
+ const sessionsDir = path.join(claudeDir, 'sessions');
13
+
14
+ const result = {
15
+ projectPath,
16
+ claudeDirExists: fs.existsSync(claudeDir),
17
+ sessionsDirExists: fs.existsSync(sessionsDir),
18
+ created: false,
19
+ error: null
20
+ };
21
+
22
+ // 如果目录不存在,自动创建
23
+ if (!result.sessionsDirExists) {
24
+ try {
25
+ fs.mkdirSync(sessionsDir, { recursive: true });
26
+ result.created = true;
27
+ result.sessionsDirExists = true;
28
+ result.claudeDirExists = true;
29
+ console.log(`[Health Check] Created .claude/sessions directory for: ${projectPath}`);
30
+ } catch (err) {
31
+ result.error = `Failed to create directory: ${err.message}`;
32
+ console.error(`[Health Check] Error creating directory for ${projectPath}:`, err);
33
+ }
34
+ }
35
+
36
+ return result;
37
+ } catch (err) {
38
+ return {
39
+ projectPath,
40
+ error: err.message
41
+ };
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 批量检查所有项目的健康状态
47
+ * @param {Array} projects - 项目列表
48
+ * @returns {Object} 汇总结果
49
+ */
50
+ function healthCheckAllProjects(projects) {
51
+ const results = [];
52
+ let checkedCount = 0;
53
+ let createdCount = 0;
54
+ let errorCount = 0;
55
+
56
+ for (const project of projects) {
57
+ if (!project.fullPath) continue;
58
+
59
+ const result = ensureProjectClaudeDir(project.fullPath);
60
+ results.push(result);
61
+ checkedCount++;
62
+
63
+ if (result.created) {
64
+ createdCount++;
65
+ }
66
+ if (result.error) {
67
+ errorCount++;
68
+ }
69
+ }
70
+
71
+ return {
72
+ summary: {
73
+ total: checkedCount,
74
+ created: createdCount,
75
+ errors: errorCount,
76
+ healthy: checkedCount - errorCount
77
+ },
78
+ results
79
+ };
80
+ }
81
+
82
+ module.exports = {
83
+ ensureProjectClaudeDir,
84
+ healthCheckAllProjects
85
+ };