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/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
- const homeDir = os.homedir();
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 { showHeader = true } = options;
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
- const timestamp = getLocalTimestamp();
29
- const cacheItems = [
30
- 'history.jsonl', // 历史记录
31
- 'debug', // 调试信息
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
- if (fs.existsSync(claudeDir)) {
50
- console.log('📂 开始清理 ~/.claude 目录下的缓存文件...\n');
36
+ console.log(`📂 正在清理目录: ${targetDir}...
37
+ `);
51
38
 
52
- // 遍历并清理缓存项
53
- for (const item of cacheItems) {
54
- const itemPath = path.join(claudeDir, item);
39
+ // Clean specific items
40
+ for (const item of itemsToClean) {
41
+ const itemPath = path.join(targetDir, item);
55
42
 
56
- try {
57
- if (fs.existsSync(itemPath)) {
58
- const stat = fs.statSync(itemPath);
59
- const backupPath = `${itemPath}-backup-${timestamp}`;
60
-
61
- if (stat.isDirectory()) {
62
- console.log(`📦 备份并清理目录: ${item}/`);
63
- } else {
64
- console.log(`📦 备份并清理文件: ${item}`);
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
- } catch (e) {
72
- console.log(`⚠️ 处理 ${item} 时出错: ${e.message}`);
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
- // 清理旧的备份文件夹(.claude-* 格式)
77
- try {
78
- const items = fs.readdirSync(claudeDir);
79
- for (const item of items) {
80
- if (item.startsWith('.claude-') || item.startsWith('backup-')) {
81
- const itemPath = path.join(claudeDir, item);
82
- const stat = fs.statSync(itemPath);
83
-
84
- if (stat.isDirectory()) {
85
- console.log(`🗑️ 删除旧备份: ${item}/`);
86
- // 使用 Node.js 原生 API,跨平台兼容
87
- fs.rmSync(itemPath, { recursive: true, force: true });
88
- cleanedCount++;
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
- console.log('\n✅ 已保留以下配置和工具:');
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
- // 生成本地时间戳,格式:YYYYMMDDHHMMSS
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
- // 修改 hosts 文件的函数
18
- function modifyHostsFile() {
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
- const domains = [
25
- 'statsig.anthropic.com',
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('🔧 开始修改 hosts 文件...');
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 文件,请确保以管理员/root权限运行');
43
+ console.error('❌ 无法读取 hosts 文件。请确保以管理员/root权限运行。');
38
44
  return false;
39
45
  }
40
46
 
41
- // 备份 hosts 文件
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
- // 移除所有包含目标域名的现有条目(无论指向什么IP)
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
- if (trimmed.startsWith('#')) {
57
- // 如果是之前添加的标记注释,也移除
58
- if (trimmed.includes('Added by aihezu ccclear tool')) {
59
- return false;
60
- }
61
- return true;
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
- return !domains.some(domain => {
65
- const regex = new RegExp('\\s+' + domain.replace('.', '\\.') + '(\\s|$)', 'i');
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 newEntries = [
80
+ // Build new entries
81
+ const newLines = [
72
82
  '',
73
- '# Added by aihezu ccclear tool',
74
- '127.0.0.1 statsig.anthropic.com',
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' + newEntries.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
- console.log(' 已添加/更新以下域名解析:');
85
- console.log(' - statsig.anthropic.com -> 127.0.0.1');
86
- console.log(' - api.anthropic.com -> 127.0.0.1');
91
+ console.log('✅ hosts 文件更新成功!');
92
+ hostEntries.forEach(entry => {
93
+ console.log(` - ${entry.domain} -> ${entry.ip}`);
94
+ });
87
95
 
88
- // 刷新 DNS 缓存
96
+ // Flush DNS
89
97
  console.log('🔄 刷新 DNS 缓存...');
90
- try {
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 文件,请确保以管理员/root权限运行');
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 文件失败:', error.message);
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": "1.8.0",
4
- "description": "Claude Code CLI 清理工具 - 快速备份和清理 Claude Code 的本地配置和缓存,同时修改 hosts 文件实现本地代理",
5
- "main": "bin/ccclear.js",
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/ccclear.js"
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
- "cli",
18
- "clear",
19
- "cache",
20
- "cleanup",
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
+ };