aihezu 1.8.0 → 2.0.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/bin/aihezu.js +97 -55
- package/bin/ccclear.js +6 -51
- package/bin/ccinstall.js +8 -479
- package/commands/clear.js +34 -0
- package/commands/install.js +164 -0
- package/lib/cache.js +51 -71
- package/lib/hosts.js +73 -71
- package/package.json +12 -10
- package/services/claude.js +64 -0
- package/services/codex.js +119 -0
- package/services/gemini.js +50 -0
package/lib/cache.js
CHANGED
|
@@ -2,10 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const claudeDir = path.join(homeDir, '.claude');
|
|
7
|
-
|
|
8
|
-
// 生成本地时间戳,格式:YYYYMMDDHHMMSS
|
|
5
|
+
// Generate local timestamp: YYYYMMDDHHMMSS
|
|
9
6
|
function getLocalTimestamp() {
|
|
10
7
|
const now = new Date();
|
|
11
8
|
const year = now.getFullYear();
|
|
@@ -17,91 +14,74 @@ function getLocalTimestamp() {
|
|
|
17
14
|
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
// 清理缓存函数
|
|
21
17
|
function cleanCache(options = {}) {
|
|
22
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
targetDir,
|
|
20
|
+
itemsToClean = [],
|
|
21
|
+
showHeader = true
|
|
22
|
+
} = options;
|
|
23
23
|
|
|
24
24
|
if (showHeader) {
|
|
25
25
|
console.log('\n=== 清理缓存 ===\n');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'file-history', // 文件历史
|
|
33
|
-
'session-env', // 会话环境
|
|
34
|
-
'shell-snapshots', // Shell 快照
|
|
35
|
-
'statsig', // 统计信息
|
|
36
|
-
'todos' // 待办事项
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
// 需要保留的配置和工具(不清理)
|
|
40
|
-
// - settings.json (配置文件)
|
|
41
|
-
// - commands/ (自定义命令)
|
|
42
|
-
// - skills/ (技能)
|
|
43
|
-
// - mcp/ (MCP 服务器)
|
|
44
|
-
// - projects/ (项目信息)
|
|
45
|
-
// - ide/ (IDE 配置)
|
|
28
|
+
if (!targetDir || !fs.existsSync(targetDir)) {
|
|
29
|
+
console.log(`ℹ️ 未找到目录: ${targetDir}`);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
46
32
|
|
|
33
|
+
const timestamp = getLocalTimestamp();
|
|
47
34
|
let cleanedCount = 0;
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
console.log(`📂 正在清理目录: ${targetDir}...
|
|
37
|
+
`);
|
|
51
38
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
// Clean specific items
|
|
40
|
+
for (const item of itemsToClean) {
|
|
41
|
+
const itemPath = path.join(targetDir, item);
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 使用 Node.js 原生 API,跨平台兼容
|
|
68
|
-
fs.renameSync(itemPath, backupPath);
|
|
69
|
-
cleanedCount++;
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(itemPath)) {
|
|
45
|
+
const stat = fs.statSync(itemPath);
|
|
46
|
+
const backupPath = `${itemPath}-backup-${timestamp}`;
|
|
47
|
+
|
|
48
|
+
if (stat.isDirectory()) {
|
|
49
|
+
console.log(`📦 备份并清理目录: ${item}/`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`📦 备份并清理文件: ${item}`);
|
|
70
52
|
}
|
|
71
|
-
|
|
72
|
-
|
|
53
|
+
|
|
54
|
+
fs.renameSync(itemPath, backupPath);
|
|
55
|
+
cleanedCount++;
|
|
73
56
|
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.log(`⚠️ 处理 ${item} 时出错: ${e.message}`);
|
|
74
59
|
}
|
|
60
|
+
}
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
62
|
+
// Clean old backups (generic logic: starting with 'backup-' or hidden backups)
|
|
63
|
+
try {
|
|
64
|
+
const items = fs.readdirSync(targetDir);
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
// Logic for backups: usually we renamed them to 'name-backup-timestamp'
|
|
67
|
+
// Or if the original logic was specific: '.claude-*' or 'backup-*'
|
|
68
|
+
// Let's stick to generic 'backup-*' or if the file ends with '-backup-\d+'
|
|
69
|
+
|
|
70
|
+
const isBackup = item.startsWith('backup-') || /backup-\d{14}$/.test(item);
|
|
71
|
+
|
|
72
|
+
if (isBackup) {
|
|
73
|
+
const itemPath = path.join(targetDir, item);
|
|
74
|
+
const stat = fs.statSync(itemPath);
|
|
75
|
+
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
console.log(`🗑️ 删除旧备份: ${item}/`);
|
|
78
|
+
fs.rmSync(itemPath, { recursive: true, force: true });
|
|
79
|
+
cleanedCount++;
|
|
90
80
|
}
|
|
91
81
|
}
|
|
92
|
-
} catch (e) {
|
|
93
|
-
// 忽略错误
|
|
94
82
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log(' - settings.json (配置文件)');
|
|
98
|
-
console.log(' - commands/ (自定义命令)');
|
|
99
|
-
console.log(' - skills/ (技能)');
|
|
100
|
-
console.log(' - mcp/ (MCP 服务器)');
|
|
101
|
-
console.log(' - projects/ (项目信息)');
|
|
102
|
-
console.log(' - ide/ (IDE 配置)');
|
|
103
|
-
} else {
|
|
104
|
-
console.log('ℹ️ 未找到 ~/.claude 目录');
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// ignore
|
|
105
85
|
}
|
|
106
86
|
|
|
107
87
|
return cleanedCount;
|
package/lib/hosts.js
CHANGED
|
@@ -2,7 +2,7 @@ const { execSync } = require('child_process');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Generate local timestamp: YYYYMMDDHHMMSS
|
|
6
6
|
function getLocalTimestamp() {
|
|
7
7
|
const now = new Date();
|
|
8
8
|
const year = now.getFullYear();
|
|
@@ -14,31 +14,37 @@ function getLocalTimestamp() {
|
|
|
14
14
|
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Modifies the system hosts file.
|
|
19
|
+
* @param {Array<{ip: string, domain: string}>} hostEntries - Array of entries to add.
|
|
20
|
+
* @param {string} markerText - Comment marker to identify managed lines.
|
|
21
|
+
*/
|
|
22
|
+
function modifyHostsFile(hostEntries = [], markerText = '# Added by aihezu CLI') {
|
|
23
|
+
if (!hostEntries || hostEntries.length === 0) {
|
|
24
|
+
console.log('ℹ️ 此服务无需修改 hosts 文件。');
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
const isWindows = os.platform() === 'win32';
|
|
20
29
|
const hostsPath = isWindows
|
|
21
30
|
? 'C:\\Windows\\System32\\drivers\\etc\\hosts'
|
|
22
31
|
: '/etc/hosts';
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'api.anthropic.com'
|
|
27
|
-
];
|
|
33
|
+
// Extract just the domain names for filtering
|
|
34
|
+
const targetDomains = hostEntries.map(e => e.domain);
|
|
28
35
|
|
|
29
36
|
try {
|
|
30
|
-
console.log(
|
|
37
|
+
console.log(`🔧 正在修改 hosts 文件 (${hostsPath})...`);
|
|
31
38
|
|
|
32
|
-
// 读取现有 hosts 文件内容
|
|
33
39
|
let hostsContent = '';
|
|
34
40
|
try {
|
|
35
41
|
hostsContent = fs.readFileSync(hostsPath, 'utf8');
|
|
36
42
|
} catch (error) {
|
|
37
|
-
console.error('❌ 无法读取 hosts
|
|
43
|
+
console.error('❌ 无法读取 hosts 文件。请确保以管理员/root权限运行。');
|
|
38
44
|
return false;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
//
|
|
47
|
+
// Backup
|
|
42
48
|
const timestamp = getLocalTimestamp();
|
|
43
49
|
const hostsBackup = `${hostsPath}.backup-${timestamp}`;
|
|
44
50
|
try {
|
|
@@ -48,90 +54,86 @@ function modifyHostsFile() {
|
|
|
48
54
|
console.error('⚠️ 备份 hosts 文件失败:', error.message);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// Filter existing
|
|
52
58
|
const lines = hostsContent.split('\n');
|
|
53
59
|
const filteredLines = lines.filter(line => {
|
|
54
60
|
const trimmed = line.trim();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
61
|
+
|
|
62
|
+
// Remove our managed marker comments
|
|
63
|
+
// Also remove legacy markers for backward compatibility
|
|
64
|
+
if (trimmed === markerText ||
|
|
65
|
+
trimmed === '# Added by aihezu ccclear tool' ||
|
|
66
|
+
(trimmed.startsWith('#') && trimmed.includes('aihezu'))) {
|
|
67
|
+
return false;
|
|
62
68
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
|
|
70
|
+
// Keep other comments
|
|
71
|
+
if (trimmed.startsWith('#')) return true;
|
|
72
|
+
|
|
73
|
+
// Remove lines matching our target domains
|
|
74
|
+
return !targetDomains.some(domain => {
|
|
75
|
+
const regex = new RegExp('\\s+' + domain.replace('.', '\\.') + '(\s|$)', 'i');
|
|
66
76
|
return regex.test(trimmed);
|
|
67
77
|
});
|
|
68
78
|
});
|
|
69
79
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
80
|
+
// Build new entries
|
|
81
|
+
const newLines = [
|
|
72
82
|
'',
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'127.0.0.1 api.anthropic.com'
|
|
83
|
+
markerText,
|
|
84
|
+
...hostEntries.map(entry => `${entry.ip} ${entry.domain}`)
|
|
76
85
|
];
|
|
77
86
|
|
|
78
|
-
const newHostsContent = filteredLines.join('\n') + '\n' +
|
|
87
|
+
const newHostsContent = filteredLines.join('\n') + '\n' + newLines.join('\n') + '\n';
|
|
79
88
|
|
|
80
|
-
// 写入 hosts 文件
|
|
81
89
|
try {
|
|
82
90
|
fs.writeFileSync(hostsPath, newHostsContent);
|
|
83
|
-
console.log('✅ hosts
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
console.log('✅ hosts 文件更新成功!');
|
|
92
|
+
hostEntries.forEach(entry => {
|
|
93
|
+
console.log(` - ${entry.domain} -> ${entry.ip}`);
|
|
94
|
+
});
|
|
87
95
|
|
|
88
|
-
//
|
|
96
|
+
// Flush DNS
|
|
89
97
|
console.log('🔄 刷新 DNS 缓存...');
|
|
90
|
-
|
|
91
|
-
if (isWindows) {
|
|
92
|
-
// Windows: 需要管理员权限
|
|
93
|
-
execSync('ipconfig /flushdns', { stdio: 'pipe' });
|
|
94
|
-
console.log(' ✅ DNS 缓存已刷新');
|
|
95
|
-
} else if (os.platform() === 'darwin') {
|
|
96
|
-
// macOS: 如果已经是 root/sudo 运行,直接执行;否则会失败但不影响主要功能
|
|
97
|
-
try {
|
|
98
|
-
execSync('dscacheutil -flushcache; killall -HUP mDNSResponder', { stdio: 'pipe' });
|
|
99
|
-
console.log(' ✅ DNS 缓存已刷新');
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.log(' ⚠️ DNS 缓存刷新失败,请稍后手动执行:');
|
|
102
|
-
console.log(' sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder');
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
// Linux
|
|
106
|
-
try {
|
|
107
|
-
execSync('systemd-resolve --flush-caches 2>/dev/null || service nscd restart 2>/dev/null', { stdio: 'pipe' });
|
|
108
|
-
console.log(' ✅ DNS 缓存已刷新');
|
|
109
|
-
} catch (e) {
|
|
110
|
-
console.log(' ⚠️ DNS 缓存刷新失败,请稍后手动执行:');
|
|
111
|
-
console.log(' sudo systemd-resolve --flush-caches');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
if (isWindows) {
|
|
116
|
-
console.log(' ⚠️ DNS 缓存刷新失败,请以管理员身份运行命令提示符');
|
|
117
|
-
} else {
|
|
118
|
-
console.log(' ℹ️ DNS 缓存刷新失败(可能需要 root 权限)');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
98
|
+
flushDNS(isWindows);
|
|
122
99
|
return true;
|
|
123
100
|
} catch (error) {
|
|
124
|
-
console.error('❌ 无法写入 hosts
|
|
125
|
-
console.error(' macOS/Linux: 使用 sudo 运行');
|
|
126
|
-
console.error(' Windows: 以管理员身份运行命令提示符');
|
|
101
|
+
console.error('❌ 无法写入 hosts 文件。权限不足?');
|
|
127
102
|
return false;
|
|
128
103
|
}
|
|
129
104
|
} catch (error) {
|
|
130
|
-
console.error('❌ 修改 hosts
|
|
105
|
+
console.error('❌ 修改 hosts 文件出错:', error.message);
|
|
131
106
|
return false;
|
|
132
107
|
}
|
|
133
108
|
}
|
|
134
109
|
|
|
110
|
+
function flushDNS(isWindows) {
|
|
111
|
+
try {
|
|
112
|
+
if (isWindows) {
|
|
113
|
+
execSync('ipconfig /flushdns', { stdio: 'pipe' });
|
|
114
|
+
console.log(' ✅ DNS 缓存已刷新。');
|
|
115
|
+
} else if (os.platform() === 'darwin') {
|
|
116
|
+
try {
|
|
117
|
+
execSync('dscacheutil -flushcache; killall -HUP mDNSResponder', { stdio: 'pipe' });
|
|
118
|
+
console.log(' ✅ DNS 缓存已刷新。');
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.log(' ⚠️ DNS 刷新失败 (请尝试 sudo): sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder');
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// Linux
|
|
124
|
+
try {
|
|
125
|
+
execSync('systemd-resolve --flush-caches 2>/dev/null || service nscd restart 2>/dev/null', { stdio: 'pipe' });
|
|
126
|
+
console.log(' ✅ DNS 缓存已刷新。');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.log(' ⚠️ DNS 刷新失败。请尝试: sudo systemd-resolve --flush-caches');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.log(' ⚠️ DNS 刷新失败。');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
135
136
|
module.exports = {
|
|
136
|
-
modifyHostsFile
|
|
137
|
+
modifyHostsFile,
|
|
138
|
+
getLocalTimestamp
|
|
137
139
|
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aihezu",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "bin/
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "AI 开发环境配置工具 - 支持 Claude Code, Codex, Google Gemini 的本地化配置、代理设置与缓存清理",
|
|
5
|
+
"main": "bin/aihezu.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aihezu": "bin/aihezu.js",
|
|
8
8
|
"ccclear": "bin/ccclear.js",
|
|
9
9
|
"ccinstall": "bin/ccinstall.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "node bin/
|
|
12
|
+
"test": "node bin/aihezu.js help"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"cli",
|
|
17
|
+
"proxy",
|
|
15
18
|
"claude",
|
|
16
19
|
"claude-code",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"anthropic"
|
|
20
|
+
"codex",
|
|
21
|
+
"gemini",
|
|
22
|
+
"configuration",
|
|
23
|
+
"hosts"
|
|
22
24
|
],
|
|
23
25
|
"author": "aihezu",
|
|
24
26
|
"license": "MIT",
|
|
@@ -33,4 +35,4 @@
|
|
|
33
35
|
"url": "https://github.com/aihezu/npm-ccclear/issues"
|
|
34
36
|
},
|
|
35
37
|
"homepage": "https://github.com/aihezu/npm-ccclear#readme"
|
|
36
|
-
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const homeDir = os.homedir();
|
|
6
|
+
const configDir = path.join(homeDir, '.claude');
|
|
7
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: 'claude',
|
|
11
|
+
displayName: 'Claude Code',
|
|
12
|
+
defaultApiUrl: 'https://cc.aihezu.dev/api',
|
|
13
|
+
|
|
14
|
+
// Cache cleaning configuration
|
|
15
|
+
cacheConfig: {
|
|
16
|
+
dir: configDir,
|
|
17
|
+
items: [
|
|
18
|
+
'history.jsonl',
|
|
19
|
+
'debug',
|
|
20
|
+
'file-history',
|
|
21
|
+
'session-env',
|
|
22
|
+
'shell-snapshots',
|
|
23
|
+
'statsig',
|
|
24
|
+
'todos'
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Hosts file configuration
|
|
29
|
+
hostsConfig: [
|
|
30
|
+
{ domain: 'statsig.anthropic.com', ip: '127.0.0.1' },
|
|
31
|
+
{ domain: 'api.anthropic.com', ip: '127.0.0.1' }
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// Configuration setup logic
|
|
35
|
+
setupConfig: async (apiKey, apiUrl, options = {}) => {
|
|
36
|
+
// Ensure directory exists
|
|
37
|
+
if (!fs.existsSync(configDir)) {
|
|
38
|
+
console.log('📁 创建 ~/.claude 目录...');
|
|
39
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Read existing settings
|
|
43
|
+
let settings = {};
|
|
44
|
+
if (fs.existsSync(settingsPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(settingsPath, 'utf8');
|
|
47
|
+
settings = JSON.parse(content);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.log('⚠️ 现有配置文件格式错误,将创建新的配置。');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Update settings
|
|
54
|
+
settings.env = {
|
|
55
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
56
|
+
ANTHROPIC_BASE_URL: apiUrl
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Write file
|
|
60
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
61
|
+
|
|
62
|
+
return [settingsPath];
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const homeDir = os.homedir();
|
|
6
|
+
const configDir = path.join(homeDir, '.codex');
|
|
7
|
+
const configPath = path.join(configDir, 'config.toml');
|
|
8
|
+
const authPath = path.join(configDir, 'auth.json');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: 'codex',
|
|
12
|
+
displayName: 'Codex',
|
|
13
|
+
defaultApiUrl: 'https://cc.aihezu.dev/openai',
|
|
14
|
+
|
|
15
|
+
// Cache cleaning configuration (minimal for Codex usually)
|
|
16
|
+
cacheConfig: {
|
|
17
|
+
dir: configDir,
|
|
18
|
+
items: [] // Add items if needed in future
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Hosts file configuration (Codex usually doesn't need hosts mod if config allows URL change)
|
|
22
|
+
hostsConfig: [],
|
|
23
|
+
|
|
24
|
+
// Configuration setup logic
|
|
25
|
+
setupConfig: async (apiKey, apiUrl, options = {}) => {
|
|
26
|
+
const { modelName = 'gpt-5-codex' } = options;
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(configDir)) {
|
|
29
|
+
console.log('📁 创建 ~/.codex 目录...');
|
|
30
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle config.toml
|
|
34
|
+
let configContent = '';
|
|
35
|
+
let existingConfig = '';
|
|
36
|
+
let providerName = 'aihezu';
|
|
37
|
+
|
|
38
|
+
if (fs.existsSync(configPath)) {
|
|
39
|
+
existingConfig = fs.readFileSync(configPath, 'utf8');
|
|
40
|
+
|
|
41
|
+
const providerMatch = existingConfig.match(/model_provider\s*=\s*"([^"]+)"/);
|
|
42
|
+
if (providerMatch) {
|
|
43
|
+
providerName = providerMatch[1];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Update logic (simplified from original)
|
|
47
|
+
let newConfig = existingConfig;
|
|
48
|
+
|
|
49
|
+
// Update model
|
|
50
|
+
const modelPattern = /^model\s*=\s*"[^"]*"/m;
|
|
51
|
+
if (modelPattern.test(newConfig)) {
|
|
52
|
+
newConfig = newConfig.replace(modelPattern, `model = "${modelName}"`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update base_url
|
|
56
|
+
const baseUrlPattern = new RegExp(
|
|
57
|
+
`(\[model_providers\.${providerName}\][\s\S]*?base_url\s*=\s*)"[^"]*"`,
|
|
58
|
+
'm'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (baseUrlPattern.test(newConfig)) {
|
|
62
|
+
newConfig = newConfig.replace(baseUrlPattern, `$1"${apiUrl}"`);
|
|
63
|
+
} else {
|
|
64
|
+
// If provider block exists but no base_url (rare), or provider block missing
|
|
65
|
+
const providerSectionPattern = new RegExp(`\[model_providers\.${providerName}\]`, 'm');
|
|
66
|
+
if (providerSectionPattern.test(newConfig)) {
|
|
67
|
+
newConfig = newConfig.replace(
|
|
68
|
+
providerSectionPattern,
|
|
69
|
+
`[model_providers.${providerName}]\nbase_url = "${apiUrl}"`
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
// Append new provider
|
|
73
|
+
newConfig = newConfig.trim() + '\n\n' +
|
|
74
|
+
`[model_providers.${providerName}]\n` +
|
|
75
|
+
`name = "${providerName}"\n` +
|
|
76
|
+
`base_url = "${apiUrl}"\n` +
|
|
77
|
+
`wire_api = "responses"\n` +
|
|
78
|
+
`requires_openai_auth = true\n` +
|
|
79
|
+
`env_key = "AIHEZU_OAI_KEY"\n`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
configContent = newConfig;
|
|
83
|
+
|
|
84
|
+
} else {
|
|
85
|
+
// New Config
|
|
86
|
+
configContent = [
|
|
87
|
+
'model_provider = "aihezu"',
|
|
88
|
+
`model = "${modelName}"`,
|
|
89
|
+
'model_reasoning_effort = "high"',
|
|
90
|
+
'disable_response_storage = true',
|
|
91
|
+
'preferred_auth_method = "apikey"',
|
|
92
|
+
'',
|
|
93
|
+
'[model_providers.aihezu]',
|
|
94
|
+
'name = "aihezu"',
|
|
95
|
+
`base_url = "${apiUrl}"`,
|
|
96
|
+
'wire_api = "responses"',
|
|
97
|
+
'requires_openai_auth = true',
|
|
98
|
+
'env_key = "AIHEZU_OAI_KEY"',
|
|
99
|
+
''
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fs.writeFileSync(configPath, configContent, 'utf8');
|
|
104
|
+
|
|
105
|
+
// Handle auth.json
|
|
106
|
+
let authData = {};
|
|
107
|
+
if (fs.existsSync(authPath)) {
|
|
108
|
+
try {
|
|
109
|
+
authData = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
authData = {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
authData.AIHEZU_OAI_KEY = apiKey;
|
|
115
|
+
fs.writeFileSync(authPath, JSON.stringify(authData, null, 2), 'utf8');
|
|
116
|
+
|
|
117
|
+
return [configPath, authPath];
|
|
118
|
+
}
|
|
119
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const homeDir = os.homedir();
|
|
6
|
+
const configDir = path.join(homeDir, '.gemini');
|
|
7
|
+
const envFilePath = path.join(configDir, '.env');
|
|
8
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: 'gemini',
|
|
12
|
+
displayName: 'Google Gemini',
|
|
13
|
+
defaultApiUrl: 'https://cc.aihezu.dev/gemini',
|
|
14
|
+
|
|
15
|
+
// Cache cleaning configuration
|
|
16
|
+
cacheConfig: {
|
|
17
|
+
dir: configDir,
|
|
18
|
+
items: ['cache', 'logs']
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Hosts file configuration
|
|
22
|
+
// Since we use .env for GOOGLE_GEMINI_BASE_URL, we don't need hosts mod anymore!
|
|
23
|
+
hostsConfig: [],
|
|
24
|
+
|
|
25
|
+
// Configuration setup logic
|
|
26
|
+
setupConfig: async (apiKey, apiUrl, options = {}) => {
|
|
27
|
+
if (!fs.existsSync(configDir)) {
|
|
28
|
+
console.log('📁 创建 ~/.gemini 目录...');
|
|
29
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 1. Write to .env (Preferred method verified by user)
|
|
33
|
+
const envContent = [
|
|
34
|
+
`GEMINI_API_KEY="${apiKey}"`,
|
|
35
|
+
`GOOGLE_GEMINI_BASE_URL="${apiUrl}"`,
|
|
36
|
+
'' // End with newline
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
fs.writeFileSync(envFilePath, envContent, 'utf8');
|
|
40
|
+
|
|
41
|
+
// 2. Also keep settings.json but keep it clean (no duplicate sensitive data)
|
|
42
|
+
// We only store extra options here if any.
|
|
43
|
+
const settings = {
|
|
44
|
+
...options
|
|
45
|
+
};
|
|
46
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
47
|
+
|
|
48
|
+
return [envFilePath, settingsPath];
|
|
49
|
+
}
|
|
50
|
+
};
|