coding-tool-x 3.3.7 → 3.3.9
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 +20 -0
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-D7KX7iF8.js +1 -0
- package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-CL-qpoJ_.js +2 -0
- package/dist/web/assets/index-D_5dRFOL.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +14 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/commands/toggle-proxy.js +100 -5
- package/src/config/paths.js +718 -90
- package/src/server/api/agents.js +1 -1
- package/src/server/api/channels.js +9 -0
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-channels.js +9 -0
- package/src/server/api/codex-proxy.js +27 -15
- package/src/server/api/gemini-proxy.js +22 -11
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/oauth-credentials.js +163 -0
- package/src/server/api/opencode-proxy.js +22 -10
- package/src/server/api/plugins.js +2 -1
- package/src/server/api/proxy.js +39 -44
- package/src/server/api/skills.js +91 -13
- package/src/server/api/ui-config.js +5 -0
- package/src/server/codex-proxy-server.js +90 -70
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +2 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +21 -24
- package/src/server/services/codex-channels.js +158 -255
- package/src/server/services/codex-config.js +2 -5
- package/src/server/services/codex-env-manager.js +423 -0
- package/src/server/services/codex-settings-manager.js +21 -357
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +43 -9
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +14 -12
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +35 -19
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-keychain.js +243 -0
- package/src/server/services/native-oauth-adapters.js +891 -0
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/oauth-credentials-service.js +786 -0
- package/src/server/services/oauth-utils.js +49 -0
- package/src/server/services/opencode-channels.js +19 -15
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-settings-manager.js +169 -16
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +115 -15
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/settings-manager.js +13 -0
- package/src/server/services/skill-service.js +867 -368
- package/src/server/services/statistics-service.js +5 -5
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +1 -1
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Home-BsSioaaB.css +0 -1
- package/dist/web/assets/Home-obifg_9E.js +0 -1
- package/dist/web/assets/index-C7LPdVsN.js +0 -2
- package/dist/web/assets/index-eEmjZKWP.css +0 -1
- package/docs/bannel.png +0 -0
- package/docs/model-redirection.md +0 -251
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const toml = require('toml');
|
|
5
4
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
5
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
6
|
+
const { syncCodexUserEnvironment } = require('./codex-env-manager');
|
|
9
7
|
|
|
10
8
|
// Codex 配置文件路径
|
|
11
9
|
function getConfigPath() {
|
|
12
|
-
return
|
|
10
|
+
return NATIVE_PATHS.codex.config;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
function getAuthPath() {
|
|
16
|
-
return
|
|
14
|
+
return NATIVE_PATHS.codex.auth;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
// 备份文件路径
|
|
20
18
|
function getConfigBackupPath() {
|
|
21
|
-
return
|
|
19
|
+
return NATIVE_PATHS.codex.configBackup;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
function getAuthBackupPath() {
|
|
25
|
-
return
|
|
23
|
+
return NATIVE_PATHS.codex.authBackup;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
// 检查配置文件是否存在
|
|
@@ -39,172 +37,6 @@ function hasBackup() {
|
|
|
39
37
|
return fs.existsSync(getConfigBackupPath()) || fs.existsSync(getAuthBackupPath());
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
const INVALID_ENV_NAME_PATTERN = /[\r\n]/;
|
|
43
|
-
const SHELL_MARKER_PREFIX = '# Added by Coding-Tool for Codex';
|
|
44
|
-
|
|
45
|
-
function normalizeEnvName(envName) {
|
|
46
|
-
const normalized = String(envName || '').trim();
|
|
47
|
-
if (!normalized || INVALID_ENV_NAME_PATTERN.test(normalized)) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return normalized;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function ensureParentDir(filePath) {
|
|
54
|
-
const dirPath = path.dirname(filePath);
|
|
55
|
-
if (!fs.existsSync(dirPath)) {
|
|
56
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function writeFileAtomic(filePath, content) {
|
|
61
|
-
ensureParentDir(filePath);
|
|
62
|
-
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
fs.writeFileSync(tempPath, content, 'utf8');
|
|
66
|
-
fs.renameSync(tempPath, filePath);
|
|
67
|
-
} finally {
|
|
68
|
-
if (fs.existsSync(tempPath)) {
|
|
69
|
-
try {
|
|
70
|
-
fs.unlinkSync(tempPath);
|
|
71
|
-
} catch (cleanupErr) {
|
|
72
|
-
// ignore cleanup errors
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function normalizeHomePath(filePath) {
|
|
79
|
-
const normalizedPath = String(filePath || '').replace(/\\/g, '/');
|
|
80
|
-
const normalizedHome = HOME_DIR.replace(/\\/g, '/');
|
|
81
|
-
if (normalizedPath.startsWith(normalizedHome)) {
|
|
82
|
-
return `~${normalizedPath.slice(normalizedHome.length)}`;
|
|
83
|
-
}
|
|
84
|
-
return filePath;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function compactBlankLines(lines) {
|
|
88
|
-
const compacted = [];
|
|
89
|
-
let previousIsBlank = false;
|
|
90
|
-
|
|
91
|
-
for (const line of lines) {
|
|
92
|
-
const isBlank = line.trim() === '';
|
|
93
|
-
if (isBlank) {
|
|
94
|
-
if (!previousIsBlank) {
|
|
95
|
-
compacted.push('');
|
|
96
|
-
}
|
|
97
|
-
previousIsBlank = true;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
compacted.push(line);
|
|
102
|
-
previousIsBlank = false;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
while (compacted.length > 0 && compacted[compacted.length - 1].trim() === '') {
|
|
106
|
-
compacted.pop();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return compacted;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function isPowerShellProfile(filePath) {
|
|
113
|
-
return String(filePath || '').toLowerCase().endsWith('.ps1');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function getShellConfigCandidates() {
|
|
117
|
-
const homeDir = HOME_DIR;
|
|
118
|
-
const shell = String(process.env.SHELL || '').toLowerCase();
|
|
119
|
-
const candidates = [];
|
|
120
|
-
|
|
121
|
-
if (isWindowsLikePlatform(process.platform, process.env)) {
|
|
122
|
-
const oneDriveDir = process.env.OneDrive || process.env.ONEDRIVE || '';
|
|
123
|
-
|
|
124
|
-
if (shell.includes('zsh')) {
|
|
125
|
-
candidates.push(path.join(homeDir, '.zshrc'));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (shell.includes('bash')) {
|
|
129
|
-
candidates.push(path.join(homeDir, '.bashrc'));
|
|
130
|
-
candidates.push(path.join(homeDir, '.bash_profile'));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
candidates.push(path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
134
|
-
candidates.push(path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
135
|
-
if (oneDriveDir) {
|
|
136
|
-
candidates.push(path.join(oneDriveDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
137
|
-
candidates.push(path.join(oneDriveDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
138
|
-
}
|
|
139
|
-
candidates.push(path.join(homeDir, '.bashrc'));
|
|
140
|
-
candidates.push(path.join(homeDir, '.profile'));
|
|
141
|
-
} else if (shell.includes('zsh')) {
|
|
142
|
-
candidates.push(path.join(homeDir, '.zshrc'));
|
|
143
|
-
candidates.push(path.join(homeDir, '.zprofile'));
|
|
144
|
-
candidates.push(path.join(homeDir, '.profile'));
|
|
145
|
-
} else if (shell.includes('bash')) {
|
|
146
|
-
if (process.platform === 'darwin') {
|
|
147
|
-
candidates.push(path.join(homeDir, '.bash_profile'));
|
|
148
|
-
candidates.push(path.join(homeDir, '.bashrc'));
|
|
149
|
-
} else {
|
|
150
|
-
candidates.push(path.join(homeDir, '.bashrc'));
|
|
151
|
-
candidates.push(path.join(homeDir, '.bash_profile'));
|
|
152
|
-
}
|
|
153
|
-
candidates.push(path.join(homeDir, '.profile'));
|
|
154
|
-
} else {
|
|
155
|
-
candidates.push(path.join(homeDir, '.zshrc'));
|
|
156
|
-
candidates.push(path.join(homeDir, '.bashrc'));
|
|
157
|
-
candidates.push(path.join(homeDir, '.bash_profile'));
|
|
158
|
-
candidates.push(path.join(homeDir, '.profile'));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return [...new Set(candidates)];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function getShellReloadCommand(configPath) {
|
|
165
|
-
if (!configPath) {
|
|
166
|
-
return isWindowsLikePlatform(process.platform, process.env) ? '重启终端' : 'source ~/.zshrc';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const displayPath = normalizeHomePath(configPath);
|
|
170
|
-
const normalized = String(displayPath || '').replace(/\\/g, '/').toLowerCase();
|
|
171
|
-
|
|
172
|
-
if (normalized.endsWith('microsoft.powershell_profile.ps1')) {
|
|
173
|
-
return '. $PROFILE';
|
|
174
|
-
}
|
|
175
|
-
if (normalized.endsWith('/.zshrc')) {
|
|
176
|
-
return 'source ~/.zshrc';
|
|
177
|
-
}
|
|
178
|
-
if (normalized.endsWith('/.bash_profile')) {
|
|
179
|
-
return 'source ~/.bash_profile';
|
|
180
|
-
}
|
|
181
|
-
if (normalized.endsWith('/.bashrc')) {
|
|
182
|
-
return 'source ~/.bashrc';
|
|
183
|
-
}
|
|
184
|
-
if (normalized.endsWith('/.profile')) {
|
|
185
|
-
return 'source ~/.profile';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (isWindowsLikePlatform(process.platform, process.env)) {
|
|
189
|
-
return '. $PROFILE';
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return `source ${displayPath}`;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function escapeShellValue(value) {
|
|
196
|
-
return String(value ?? '')
|
|
197
|
-
.replace(/\\/g, '\\\\')
|
|
198
|
-
.replace(/"/g, '\\"')
|
|
199
|
-
.replace(/\$/g, '\\$')
|
|
200
|
-
.replace(/`/g, '\\`');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function escapePowerShellValue(value) {
|
|
204
|
-
return String(value ?? '')
|
|
205
|
-
.replace(/`/g, '``')
|
|
206
|
-
.replace(/"/g, '`"');
|
|
207
|
-
}
|
|
208
40
|
|
|
209
41
|
// 读取 config.toml
|
|
210
42
|
function readConfig() {
|
|
@@ -308,7 +140,7 @@ function backupSettings() {
|
|
|
308
140
|
const configContent = fs.readFileSync(getConfigPath(), 'utf8');
|
|
309
141
|
fs.writeFileSync(getConfigBackupPath(), configContent, 'utf8');
|
|
310
142
|
|
|
311
|
-
// 备份 auth.json (
|
|
143
|
+
// 备份 auth.json (如果存在,主要用于 OAuth 状态回滚)
|
|
312
144
|
if (authExists()) {
|
|
313
145
|
const authContent = fs.readFileSync(getAuthPath(), 'utf8');
|
|
314
146
|
fs.writeFileSync(getAuthBackupPath(), authContent, 'utf8');
|
|
@@ -359,11 +191,10 @@ function restoreSettings() {
|
|
|
359
191
|
fs.unlinkSync(getAuthBackupPath());
|
|
360
192
|
}
|
|
361
193
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
delete process.env.CC_PROXY_KEY;
|
|
194
|
+
syncCodexUserEnvironment({}, {
|
|
195
|
+
replace: false,
|
|
196
|
+
removeKeys: ['CC_PROXY_KEY']
|
|
197
|
+
});
|
|
367
198
|
|
|
368
199
|
console.log('Codex settings restored from backup');
|
|
369
200
|
return { success: true };
|
|
@@ -372,161 +203,6 @@ function restoreSettings() {
|
|
|
372
203
|
}
|
|
373
204
|
}
|
|
374
205
|
|
|
375
|
-
// 获取用户的 shell 配置文件路径
|
|
376
|
-
function getShellConfigPath() {
|
|
377
|
-
const candidates = getShellConfigCandidates();
|
|
378
|
-
const existing = candidates.find(filePath => fs.existsSync(filePath));
|
|
379
|
-
return existing || candidates[0];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// 注入环境变量到 shell 配置文件
|
|
383
|
-
function injectEnvToShell(envName, envValue) {
|
|
384
|
-
const normalizedEnvName = normalizeEnvName(envName);
|
|
385
|
-
if (!normalizedEnvName) {
|
|
386
|
-
return {
|
|
387
|
-
success: false,
|
|
388
|
-
error: `Invalid environment variable name: ${envName}`,
|
|
389
|
-
isFirstTime: false
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const configPath = getShellConfigPath();
|
|
394
|
-
const marker = `${SHELL_MARKER_PREFIX} [${normalizedEnvName}]`;
|
|
395
|
-
const usePowerShell = isPowerShellProfile(configPath);
|
|
396
|
-
const exportLine = usePowerShell
|
|
397
|
-
? `$env:${normalizedEnvName} = "${escapePowerShellValue(envValue)}"`
|
|
398
|
-
: `export ${normalizedEnvName}="${escapeShellValue(envValue)}"`;
|
|
399
|
-
|
|
400
|
-
try {
|
|
401
|
-
let content = '';
|
|
402
|
-
if (fs.existsSync(configPath)) {
|
|
403
|
-
content = fs.readFileSync(configPath, 'utf8');
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const envKeyEscaped = String(normalizedEnvName).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
407
|
-
const envLineRegex = usePowerShell
|
|
408
|
-
? new RegExp(`^\\s*\\$env:${envKeyEscaped}\\s*=`, 'i')
|
|
409
|
-
: new RegExp(`^\\s*(?:export\\s+)?${envKeyEscaped}=`);
|
|
410
|
-
|
|
411
|
-
const originalLines = content ? content.split(/\r?\n/) : [];
|
|
412
|
-
const cleanedLines = [];
|
|
413
|
-
let existed = false;
|
|
414
|
-
|
|
415
|
-
for (let i = 0; i < originalLines.length; i++) {
|
|
416
|
-
const currentLine = originalLines[i];
|
|
417
|
-
const trimmedLine = currentLine.trim();
|
|
418
|
-
|
|
419
|
-
if (trimmedLine === marker) {
|
|
420
|
-
const nextLine = originalLines[i + 1] || '';
|
|
421
|
-
if (envLineRegex.test(nextLine.trim())) {
|
|
422
|
-
i += 1;
|
|
423
|
-
}
|
|
424
|
-
existed = true;
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (envLineRegex.test(trimmedLine)) {
|
|
429
|
-
existed = true;
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
cleanedLines.push(currentLine);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
while (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1].trim() === '') {
|
|
437
|
-
cleanedLines.pop();
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (cleanedLines.length > 0) {
|
|
441
|
-
cleanedLines.push('');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
cleanedLines.push(marker, exportLine);
|
|
445
|
-
|
|
446
|
-
const nextContent = `${cleanedLines.join('\n')}\n`;
|
|
447
|
-
if (nextContent !== content) {
|
|
448
|
-
writeFileAtomic(configPath, nextContent);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 同步更新当前进程的环境变量,使变更立即生效(无需新开终端)
|
|
452
|
-
process.env[normalizedEnvName] = String(envValue ?? '');
|
|
453
|
-
|
|
454
|
-
return { success: true, path: configPath, isFirstTime: !existed };
|
|
455
|
-
} catch (err) {
|
|
456
|
-
// 不抛出错误,只是警告,因为这不是致命问题
|
|
457
|
-
console.warn(`[Codex] Failed to inject env to shell config: ${err.message}`);
|
|
458
|
-
return { success: false, error: err.message, isFirstTime: false };
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// 从 shell 配置文件移除环境变量
|
|
463
|
-
function removeEnvFromShell(envName) {
|
|
464
|
-
const normalizedEnvName = normalizeEnvName(envName);
|
|
465
|
-
if (!normalizedEnvName) {
|
|
466
|
-
return {
|
|
467
|
-
success: false,
|
|
468
|
-
error: `Invalid environment variable name: ${envName}`
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const configPath = getShellConfigPath();
|
|
473
|
-
|
|
474
|
-
try {
|
|
475
|
-
if (!fs.existsSync(configPath)) {
|
|
476
|
-
return { success: true };
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
480
|
-
const usePowerShell = isPowerShellProfile(configPath);
|
|
481
|
-
const marker = `${SHELL_MARKER_PREFIX} [${normalizedEnvName}]`;
|
|
482
|
-
const envKeyEscaped = String(normalizedEnvName).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
483
|
-
const envLineRegex = usePowerShell
|
|
484
|
-
? new RegExp(`^\\s*\\$env:${envKeyEscaped}\\s*=`, 'i')
|
|
485
|
-
: new RegExp(`^\\s*(?:export\\s+)?${envKeyEscaped}=`);
|
|
486
|
-
|
|
487
|
-
const originalLines = content ? content.split(/\r?\n/) : [];
|
|
488
|
-
const cleanedLines = [];
|
|
489
|
-
let changed = false;
|
|
490
|
-
|
|
491
|
-
for (let i = 0; i < originalLines.length; i++) {
|
|
492
|
-
const currentLine = originalLines[i];
|
|
493
|
-
const trimmedLine = currentLine.trim();
|
|
494
|
-
|
|
495
|
-
if (trimmedLine === marker) {
|
|
496
|
-
const nextLine = originalLines[i + 1] || '';
|
|
497
|
-
if (envLineRegex.test(nextLine.trim())) {
|
|
498
|
-
i += 1;
|
|
499
|
-
}
|
|
500
|
-
changed = true;
|
|
501
|
-
continue;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (envLineRegex.test(trimmedLine)) {
|
|
505
|
-
changed = true;
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
cleanedLines.push(currentLine);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (!changed) {
|
|
513
|
-
return { success: true };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const normalized = compactBlankLines(cleanedLines);
|
|
517
|
-
const nextContent = normalized.length > 0 ? `${normalized.join('\n')}\n` : '';
|
|
518
|
-
writeFileAtomic(configPath, nextContent);
|
|
519
|
-
|
|
520
|
-
// 同步删除当前进程的环境变量,使变更立即生效(无需新开终端)
|
|
521
|
-
delete process.env[normalizedEnvName];
|
|
522
|
-
|
|
523
|
-
return { success: true };
|
|
524
|
-
} catch (err) {
|
|
525
|
-
console.warn(`[Codex] Failed to remove env from shell config: ${err.message}`);
|
|
526
|
-
return { success: false, error: err.message };
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
206
|
// 设置代理配置
|
|
531
207
|
function setProxyConfig(proxyPort) {
|
|
532
208
|
try {
|
|
@@ -555,29 +231,21 @@ function setProxyConfig(proxyPort) {
|
|
|
555
231
|
// 写入配置
|
|
556
232
|
writeConfig(config);
|
|
557
233
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// 注入环境变量到 shell 配置文件(解决某些系统环境变量优先级问题)
|
|
564
|
-
const shellInjectResult = injectEnvToShell('CC_PROXY_KEY', 'PROXY_KEY');
|
|
565
|
-
|
|
566
|
-
// 同步更新当前进程的环境变量,使代理立即生效(无需新开终端)
|
|
567
|
-
process.env.CC_PROXY_KEY = 'PROXY_KEY';
|
|
568
|
-
|
|
569
|
-
// 获取 shell 配置文件路径用于提示信息
|
|
570
|
-
const shellConfigPath = shellInjectResult.path || getShellConfigPath();
|
|
571
|
-
const sourceCommand = getShellReloadCommand(shellConfigPath);
|
|
234
|
+
const envResult = syncCodexUserEnvironment({
|
|
235
|
+
CC_PROXY_KEY: 'PROXY_KEY'
|
|
236
|
+
}, {
|
|
237
|
+
replace: false
|
|
238
|
+
});
|
|
572
239
|
|
|
573
240
|
console.log(`Codex settings updated to use proxy on port ${proxyPort}`);
|
|
574
241
|
return {
|
|
575
242
|
success: true,
|
|
576
243
|
port: proxyPort,
|
|
577
|
-
envInjected:
|
|
578
|
-
isFirstTime:
|
|
579
|
-
shellConfigPath: shellConfigPath,
|
|
580
|
-
sourceCommand: sourceCommand
|
|
244
|
+
envInjected: true,
|
|
245
|
+
isFirstTime: envResult.isFirstTime,
|
|
246
|
+
shellConfigPath: envResult.shellConfigPath,
|
|
247
|
+
sourceCommand: envResult.sourceCommand,
|
|
248
|
+
reloadRequired: envResult.reloadRequired
|
|
581
249
|
};
|
|
582
250
|
} catch (err) {
|
|
583
251
|
throw new Error('Failed to set proxy config: ' + err.message);
|
|
@@ -652,8 +320,4 @@ module.exports = {
|
|
|
652
320
|
setProxyConfig,
|
|
653
321
|
isProxyConfig,
|
|
654
322
|
getCurrentProxyPort,
|
|
655
|
-
// 导出环境变量注入函数供其他模块使用
|
|
656
|
-
getShellConfigPath,
|
|
657
|
-
injectEnvToShell,
|
|
658
|
-
removeEnvFromShell
|
|
659
323
|
};
|
|
@@ -4,36 +4,12 @@ const {
|
|
|
4
4
|
getDailyStatistics: getSharedDailyStatistics,
|
|
5
5
|
getTodayStatistics: getSharedTodayStatistics
|
|
6
6
|
} = require('./statistics-service');
|
|
7
|
+
const { normalizeUsageTokens, toNumber } = require('./proxy-log-helper');
|
|
7
8
|
|
|
8
9
|
const TOOL_TYPE = 'codex';
|
|
9
10
|
|
|
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 reasoning = toNumber(tokens.reasoning);
|
|
19
|
-
const cached = toNumber(tokens.cached);
|
|
20
|
-
const cacheCreation = toNumber(tokens.cacheCreation);
|
|
21
|
-
const cacheRead = toNumber(tokens.cacheRead || cached);
|
|
22
|
-
const total = toNumber(tokens.total) || (input + output + reasoning);
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
input,
|
|
26
|
-
output,
|
|
27
|
-
reasoning,
|
|
28
|
-
cached,
|
|
29
|
-
cacheCreation,
|
|
30
|
-
cacheRead,
|
|
31
|
-
total
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
11
|
function toLegacyEntryShape(entry = {}, includeName = false) {
|
|
36
|
-
const normalized =
|
|
12
|
+
const normalized = normalizeUsageTokens(TOOL_TYPE, entry.tokens || {});
|
|
37
13
|
const result = {
|
|
38
14
|
requests: toNumber(entry.requests),
|
|
39
15
|
tokens: {
|
|
@@ -126,7 +102,7 @@ function buildDailyStatistics(sharedDaily = {}, fallbackDate) {
|
|
|
126
102
|
}
|
|
127
103
|
|
|
128
104
|
function recordRequest(requestData = {}) {
|
|
129
|
-
const normalizedTokens =
|
|
105
|
+
const normalizedTokens = normalizeUsageTokens(TOOL_TYPE, requestData.tokens || {});
|
|
130
106
|
return recordSharedRequest({
|
|
131
107
|
...requestData,
|
|
132
108
|
toolType: TOOL_TYPE,
|
|
@@ -16,7 +16,7 @@ const { CommandsService } = require('./commands-service');
|
|
|
16
16
|
const { SkillService } = require('./skill-service');
|
|
17
17
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
18
18
|
|
|
19
|
-
const CONFIG_VERSION = '1.
|
|
19
|
+
const CONFIG_VERSION = '1.4.0';
|
|
20
20
|
const SKILL_FILE_ENCODING = 'base64';
|
|
21
21
|
const SKILL_IGNORE_DIRS = new Set(['.git']);
|
|
22
22
|
const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
|
|
@@ -39,11 +39,11 @@ const PLUGIN_SENSITIVE_PATTERNS = [
|
|
|
39
39
|
/\.p12$/i,
|
|
40
40
|
/\.pfx$/i
|
|
41
41
|
];
|
|
42
|
-
const CC_UI_CONFIG_PATH =
|
|
43
|
-
const CC_PROMPTS_PATH =
|
|
44
|
-
const CC_SECURITY_PATH =
|
|
45
|
-
const LEGACY_UI_CONFIG_PATH =
|
|
46
|
-
const LEGACY_NOTIFY_HOOK_PATH =
|
|
42
|
+
const CC_UI_CONFIG_PATH = PATHS.uiConfig;
|
|
43
|
+
const CC_PROMPTS_PATH = PATHS.prompts;
|
|
44
|
+
const CC_SECURITY_PATH = PATHS.security;
|
|
45
|
+
const LEGACY_UI_CONFIG_PATH = PATHS.uiConfig;
|
|
46
|
+
const LEGACY_NOTIFY_HOOK_PATH = PATHS.notifyHook;
|
|
47
47
|
const GEMINI_SETTINGS_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
|
|
48
48
|
const AGENT_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
49
49
|
const COMMAND_PLATFORMS = ['claude', 'opencode'];
|
|
@@ -58,8 +58,18 @@ function getOpenCodeConfigPaths() {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function getOpenCodeNotificationPluginPath() {
|
|
62
|
+
try {
|
|
63
|
+
const { getOpenCodeManagedPluginPath } = require('./notification-hooks');
|
|
64
|
+
return typeof getOpenCodeManagedPluginPath === 'function' ? getOpenCodeManagedPluginPath() : '';
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
function getNativeConfigSpecs() {
|
|
62
71
|
const openCodeConfigPaths = getOpenCodeConfigPaths();
|
|
72
|
+
const openCodeNotificationPluginPath = getOpenCodeNotificationPluginPath();
|
|
63
73
|
return {
|
|
64
74
|
claude: {
|
|
65
75
|
settings: { path: NATIVE_PATHS.claude.settings, format: 'json' }
|
|
@@ -75,7 +85,10 @@ function getNativeConfigSpecs() {
|
|
|
75
85
|
opencode: {
|
|
76
86
|
opencodeJsonc: { path: openCodeConfigPaths.opencodec, format: 'text' },
|
|
77
87
|
opencodeJson: { path: openCodeConfigPaths.opencode, format: 'text' },
|
|
78
|
-
configJson: { path: openCodeConfigPaths.config, format: 'text' }
|
|
88
|
+
configJson: { path: openCodeConfigPaths.config, format: 'text' },
|
|
89
|
+
...(openCodeNotificationPluginPath
|
|
90
|
+
? { codingToolNotifyPlugin: { path: openCodeNotificationPluginPath, format: 'text' } }
|
|
91
|
+
: {})
|
|
79
92
|
}
|
|
80
93
|
};
|
|
81
94
|
}
|
|
@@ -206,12 +219,13 @@ function buildExportReadme(exportData) {
|
|
|
206
219
|
- Agents / Skills / Commands
|
|
207
220
|
- 插件 (Plugins)
|
|
208
221
|
- MCP 服务器配置
|
|
222
|
+
- OAuth 凭证管理池
|
|
209
223
|
- 各平台原生配置(Claude / Codex / Gemini / OpenCode)
|
|
210
224
|
- UI 配置(主题、面板显示、排序等)
|
|
211
225
|
- Prompts 预设
|
|
212
226
|
- 安全配置
|
|
213
227
|
- 高级配置(端口、日志、性能等)
|
|
214
|
-
-
|
|
228
|
+
- 通知 Hook / 插件脚本(如 notify-hook.js)
|
|
215
229
|
|
|
216
230
|
> 注意:配置包可能包含 API Key、Webhook 等敏感信息,请妥善保管。
|
|
217
231
|
`;
|
|
@@ -857,9 +871,9 @@ function exportAllConfigs() {
|
|
|
857
871
|
// 获取 Plugins 配置
|
|
858
872
|
const plugins = exportPluginsSnapshot();
|
|
859
873
|
const nativeConfigs = exportNativeConfigs();
|
|
874
|
+
const oauthCredentials = readJsonFileSafe(PATHS.oauthCredentials);
|
|
860
875
|
|
|
861
876
|
// 读取 Markdown 配置文件
|
|
862
|
-
const { PATHS } = require('../../config/paths');
|
|
863
877
|
const markdownFiles = {};
|
|
864
878
|
const mdFileNames = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
|
|
865
879
|
|
|
@@ -914,6 +928,7 @@ function exportAllConfigs() {
|
|
|
914
928
|
security: security,
|
|
915
929
|
appConfig: appConfig,
|
|
916
930
|
nativeConfigs,
|
|
931
|
+
oauthCredentials,
|
|
917
932
|
claudeHooks: claudeHooks
|
|
918
933
|
}
|
|
919
934
|
};
|
|
@@ -979,6 +994,7 @@ async function importConfigs(importData, options = {}) {
|
|
|
979
994
|
security: { success: 0, failed: 0, skipped: 0 },
|
|
980
995
|
appConfig: { success: 0, failed: 0, skipped: 0 },
|
|
981
996
|
nativeConfigs: { success: 0, failed: 0, skipped: 0 },
|
|
997
|
+
oauthCredentials: { success: 0, failed: 0, skipped: 0 },
|
|
982
998
|
claudeHooks: { success: 0, failed: 0, skipped: 0 }
|
|
983
999
|
};
|
|
984
1000
|
|
|
@@ -1007,6 +1023,7 @@ async function importConfigs(importData, options = {}) {
|
|
|
1007
1023
|
security = null,
|
|
1008
1024
|
appConfig = null,
|
|
1009
1025
|
nativeConfigs = {},
|
|
1026
|
+
oauthCredentials = null,
|
|
1010
1027
|
claudeHooks = null
|
|
1011
1028
|
} = importData.data;
|
|
1012
1029
|
|
|
@@ -1550,6 +1567,22 @@ async function importConfigs(importData, options = {}) {
|
|
|
1550
1567
|
}
|
|
1551
1568
|
}
|
|
1552
1569
|
|
|
1570
|
+
if (oauthCredentials && typeof oauthCredentials === 'object') {
|
|
1571
|
+
try {
|
|
1572
|
+
const status = writeJsonFileAbsolute(PATHS.oauthCredentials, oauthCredentials, overwrite, { mode: 0o600 });
|
|
1573
|
+
if (status === 'success') {
|
|
1574
|
+
results.oauthCredentials.success++;
|
|
1575
|
+
} else if (status === 'skipped') {
|
|
1576
|
+
results.oauthCredentials.skipped++;
|
|
1577
|
+
} else {
|
|
1578
|
+
results.oauthCredentials.failed++;
|
|
1579
|
+
}
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
console.error('[ConfigImport] 导入 OAuth 凭证失败:', err);
|
|
1582
|
+
results.oauthCredentials.failed++;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1553
1586
|
// 导入 Claude Hooks 配置
|
|
1554
1587
|
if (claudeHooks && typeof claudeHooks === 'object') {
|
|
1555
1588
|
let didWrite = false;
|
|
@@ -1642,6 +1675,7 @@ function generateImportSummary(results) {
|
|
|
1642
1675
|
{ key: 'security', label: '安全配置' },
|
|
1643
1676
|
{ key: 'appConfig', label: '高级配置' },
|
|
1644
1677
|
{ key: 'nativeConfigs', label: '原生配置' },
|
|
1678
|
+
{ key: 'oauthCredentials', label: 'OAuth凭证' },
|
|
1645
1679
|
{ key: 'claudeHooks', label: 'Claude Hooks' }
|
|
1646
1680
|
];
|
|
1647
1681
|
|
|
@@ -13,8 +13,8 @@ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
|
13
13
|
|
|
14
14
|
// Configuration paths
|
|
15
15
|
const CC_TOOL_DIR = PATHS.base;
|
|
16
|
-
const REGISTRY_FILE =
|
|
17
|
-
const CONFIGS_DIR =
|
|
16
|
+
const REGISTRY_FILE = PATHS.configRegistry;
|
|
17
|
+
const CONFIGS_DIR = PATHS.configs;
|
|
18
18
|
|
|
19
19
|
// Claude Code native directories
|
|
20
20
|
const CLAUDE_HOME_DIR = path.dirname(NATIVE_PATHS.claude.settings);
|
|
@@ -113,6 +113,7 @@ class ConfigRegistryService {
|
|
|
113
113
|
*/
|
|
114
114
|
_ensureDirs() {
|
|
115
115
|
ensureDir(CC_TOOL_DIR);
|
|
116
|
+
ensureDir(path.dirname(this.registryPath));
|
|
116
117
|
ensureDir(this.configsDir);
|
|
117
118
|
|
|
118
119
|
for (const type of CONFIG_TYPES) {
|
|
@@ -24,7 +24,7 @@ const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../
|
|
|
24
24
|
|
|
25
25
|
// Paths
|
|
26
26
|
const HOME = HOME_DIR || os.homedir();
|
|
27
|
-
const CC_TOOL_CONFIGS =
|
|
27
|
+
const CC_TOOL_CONFIGS = PATHS.configs;
|
|
28
28
|
const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
|
|
29
29
|
const CODEX_DIR = path.join(HOME, '.codex');
|
|
30
30
|
const GEMINI_DIR = path.join(HOME, '.gemini');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const { PATHS } = require('../../config/paths');
|
|
3
4
|
|
|
4
|
-
const FAVORITES_DIR = PATHS.base;
|
|
5
5
|
const FAVORITES_FILE = PATHS.favorites;
|
|
6
6
|
|
|
7
7
|
// 内存缓存
|
|
@@ -17,8 +17,9 @@ const DEFAULT_FAVORITES = {
|
|
|
17
17
|
|
|
18
18
|
// Ensure favorites directory exists
|
|
19
19
|
function ensureFavoritesDir() {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const dir = path.dirname(FAVORITES_FILE);
|
|
21
|
+
if (!fs.existsSync(dir)) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|