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.
- package/CHANGELOG.md +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- 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
|
+
};
|