@liuzijian625/code-cli 1.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/.claude/settings.local.json +10 -0
- package/bin/cli.js +216 -0
- package/lib/config.js +201 -0
- package/lib/preset.js +77 -0
- package/package.json +21 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { applyConfig, clearConfig } from '../lib/config.js';
|
|
6
|
+
import { addPreset, listPresets, deletePreset, getPresets } from '../lib/preset.js';
|
|
7
|
+
|
|
8
|
+
const TOOL_NAMES = { codex: 'Codex', claude: 'Claude Code', gemini: 'Gemini CLI' };
|
|
9
|
+
|
|
10
|
+
async function mainMenu() {
|
|
11
|
+
console.clear();
|
|
12
|
+
console.log(chalk.cyan.bold('\ncode-cli - AI CLI 配置管理工具\n'));
|
|
13
|
+
|
|
14
|
+
const { action } = await inquirer.prompt([{
|
|
15
|
+
type: 'list',
|
|
16
|
+
name: 'action',
|
|
17
|
+
message: '请选择操作:',
|
|
18
|
+
choices: [
|
|
19
|
+
{ name: '1. 应用配置', value: 'apply' },
|
|
20
|
+
{ name: '2. 管理预设', value: 'manage' },
|
|
21
|
+
{ name: '3. 删除配置', value: 'clear' },
|
|
22
|
+
{ name: '4. 退出', value: 'exit' }
|
|
23
|
+
]
|
|
24
|
+
}]);
|
|
25
|
+
|
|
26
|
+
switch (action) {
|
|
27
|
+
case 'apply':
|
|
28
|
+
await applyConfigMenu();
|
|
29
|
+
break;
|
|
30
|
+
case 'manage':
|
|
31
|
+
await managePresetMenu();
|
|
32
|
+
break;
|
|
33
|
+
case 'clear':
|
|
34
|
+
await clearConfigMenu();
|
|
35
|
+
break;
|
|
36
|
+
case 'exit':
|
|
37
|
+
console.log(chalk.green('再见!'));
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function selectTool(message, includeAll = false) {
|
|
43
|
+
const choices = [
|
|
44
|
+
{ name: '1. Codex', value: 'codex' },
|
|
45
|
+
{ name: '2. Claude Code', value: 'claude' },
|
|
46
|
+
{ name: '3. Gemini CLI', value: 'gemini' }
|
|
47
|
+
];
|
|
48
|
+
if (includeAll) {
|
|
49
|
+
choices.push({ name: '4. 全部', value: 'all' });
|
|
50
|
+
choices.push({ name: '5. 返回', value: 'back' });
|
|
51
|
+
} else {
|
|
52
|
+
choices.push({ name: '4. 返回', value: 'back' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { tool } = await inquirer.prompt([{
|
|
56
|
+
type: 'list',
|
|
57
|
+
name: 'tool',
|
|
58
|
+
message,
|
|
59
|
+
choices
|
|
60
|
+
}]);
|
|
61
|
+
return tool;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function applyConfigMenu() {
|
|
65
|
+
const tool = await selectTool('选择要配置的工具:', true);
|
|
66
|
+
if (tool === 'back') return mainMenu();
|
|
67
|
+
|
|
68
|
+
if (tool === 'all') {
|
|
69
|
+
// 全部应用:每个工具选择各自的预设
|
|
70
|
+
for (const t of ['codex', 'claude', 'gemini']) {
|
|
71
|
+
const presets = getPresets(t);
|
|
72
|
+
if (presets.length === 0) {
|
|
73
|
+
console.log(chalk.yellow(`\n${TOOL_NAMES[t]} 暂无预设,跳过`));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const preset = await selectPreset(t, presets);
|
|
77
|
+
if (preset) {
|
|
78
|
+
await applyConfig(t, preset);
|
|
79
|
+
console.log(chalk.green(`${TOOL_NAMES[t]} 配置已应用`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await pause();
|
|
83
|
+
return mainMenu();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const presets = getPresets(tool);
|
|
87
|
+
if (presets.length === 0) {
|
|
88
|
+
console.log(chalk.yellow(`\n${TOOL_NAMES[tool]} 暂无预设,请先添加预设`));
|
|
89
|
+
await pause();
|
|
90
|
+
return mainMenu();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const preset = await selectPreset(tool, presets);
|
|
94
|
+
if (!preset) return applyConfigMenu();
|
|
95
|
+
|
|
96
|
+
await applyConfig(tool, preset);
|
|
97
|
+
console.log(chalk.green(`\n配置已应用到 ${TOOL_NAMES[tool]}`));
|
|
98
|
+
await pause();
|
|
99
|
+
return mainMenu();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function selectPreset(tool, presets) {
|
|
103
|
+
const choices = presets.map((p, i) => ({
|
|
104
|
+
name: `${i + 1}. ${p.name} (url: ${p.url})`,
|
|
105
|
+
value: p.name
|
|
106
|
+
}));
|
|
107
|
+
choices.push({ name: `${presets.length + 1}. 跳过`, value: 'back' });
|
|
108
|
+
|
|
109
|
+
const { preset } = await inquirer.prompt([{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'preset',
|
|
112
|
+
message: `选择 ${TOOL_NAMES[tool]} 的预设:`,
|
|
113
|
+
choices
|
|
114
|
+
}]);
|
|
115
|
+
|
|
116
|
+
if (preset === 'back') return null;
|
|
117
|
+
return presets.find(p => p.name === preset);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function managePresetMenu() {
|
|
121
|
+
const { action } = await inquirer.prompt([{
|
|
122
|
+
type: 'list',
|
|
123
|
+
name: 'action',
|
|
124
|
+
message: '预设管理:',
|
|
125
|
+
choices: [
|
|
126
|
+
{ name: '1. 添加预设', value: 'add' },
|
|
127
|
+
{ name: '2. 查看预设', value: 'list' },
|
|
128
|
+
{ name: '3. 删除预设', value: 'delete' },
|
|
129
|
+
{ name: '4. 返回', value: 'back' }
|
|
130
|
+
]
|
|
131
|
+
}]);
|
|
132
|
+
|
|
133
|
+
switch (action) {
|
|
134
|
+
case 'add':
|
|
135
|
+
await addPresetMenu();
|
|
136
|
+
break;
|
|
137
|
+
case 'list':
|
|
138
|
+
listPresets();
|
|
139
|
+
await pause();
|
|
140
|
+
break;
|
|
141
|
+
case 'delete':
|
|
142
|
+
await deletePresetMenu();
|
|
143
|
+
break;
|
|
144
|
+
case 'back':
|
|
145
|
+
return mainMenu();
|
|
146
|
+
}
|
|
147
|
+
return managePresetMenu();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function addPresetMenu() {
|
|
151
|
+
const tool = await selectTool('选择要添加预设的工具:');
|
|
152
|
+
if (tool === 'back') return;
|
|
153
|
+
|
|
154
|
+
const answers = await inquirer.prompt([
|
|
155
|
+
{ type: 'input', name: 'name', message: '请输入预设名称:' },
|
|
156
|
+
{ type: 'input', name: 'url', message: '请输入 URL:' },
|
|
157
|
+
{ type: 'password', name: 'key', message: '请输入 API Key:', mask: '*' }
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
if (!answers.name || !answers.url || !answers.key) {
|
|
161
|
+
console.log(chalk.red('所有字段都必须填写'));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
addPreset(tool, answers.name, answers.url, answers.key);
|
|
166
|
+
console.log(chalk.green(`\n${TOOL_NAMES[tool]} 预设 '${answers.name}' 已保存!`));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function deletePresetMenu() {
|
|
170
|
+
const tool = await selectTool('选择要删除预设的工具:');
|
|
171
|
+
if (tool === 'back') return;
|
|
172
|
+
|
|
173
|
+
const presets = getPresets(tool);
|
|
174
|
+
if (presets.length === 0) {
|
|
175
|
+
console.log(chalk.yellow(`\n${TOOL_NAMES[tool]} 暂无预设`));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const choices = presets.map((p, i) => ({
|
|
180
|
+
name: `${i + 1}. ${p.name}`,
|
|
181
|
+
value: p.name
|
|
182
|
+
}));
|
|
183
|
+
choices.push({ name: `${presets.length + 1}. 返回`, value: 'back' });
|
|
184
|
+
|
|
185
|
+
const { preset } = await inquirer.prompt([{
|
|
186
|
+
type: 'list',
|
|
187
|
+
name: 'preset',
|
|
188
|
+
message: '选择要删除的预设:',
|
|
189
|
+
choices
|
|
190
|
+
}]);
|
|
191
|
+
|
|
192
|
+
if (preset === 'back') return;
|
|
193
|
+
|
|
194
|
+
deletePreset(tool, preset);
|
|
195
|
+
console.log(chalk.green(`\n预设 '${preset}' 已删除!`));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function clearConfigMenu() {
|
|
199
|
+
const tool = await selectTool('选择要清除配置的工具:', true);
|
|
200
|
+
if (tool === 'back') return mainMenu();
|
|
201
|
+
|
|
202
|
+
await clearConfig(tool);
|
|
203
|
+
console.log(chalk.green(`\n${tool === 'all' ? '所有工具' : TOOL_NAMES[tool]} 的配置已清除`));
|
|
204
|
+
await pause();
|
|
205
|
+
return mainMenu();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function pause() {
|
|
209
|
+
await inquirer.prompt([{
|
|
210
|
+
type: 'input',
|
|
211
|
+
name: 'continue',
|
|
212
|
+
message: '按回车继续...'
|
|
213
|
+
}]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
mainMenu().catch(console.error);
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { parse, stringify } from '@iarna/toml';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const isWindows = os.platform() === 'win32';
|
|
10
|
+
|
|
11
|
+
// Codex 配置
|
|
12
|
+
async function applyCodex(preset) {
|
|
13
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
14
|
+
const configFile = path.join(codexDir, 'config.toml');
|
|
15
|
+
const authFile = path.join(codexDir, 'auth.json');
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(codexDir)) {
|
|
18
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 更新 config.toml
|
|
22
|
+
let config = {};
|
|
23
|
+
if (fs.existsSync(configFile)) {
|
|
24
|
+
config = parse(fs.readFileSync(configFile, 'utf-8'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
config.model_provider = 'my';
|
|
28
|
+
if (!config.model_providers) config.model_providers = {};
|
|
29
|
+
config.model_providers.my = {
|
|
30
|
+
name: 'openai',
|
|
31
|
+
base_url: preset.url,
|
|
32
|
+
wire_api: 'responses',
|
|
33
|
+
requires_openai_auth: true
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 确保 model_provider 在第一行,保留其他配置
|
|
37
|
+
const { model_provider, model_providers, ...rest } = config;
|
|
38
|
+
let tomlContent = `model_provider = "my"\n\n`;
|
|
39
|
+
tomlContent += stringify({ model_providers, ...rest });
|
|
40
|
+
fs.writeFileSync(configFile, tomlContent);
|
|
41
|
+
|
|
42
|
+
// 更新 auth.json
|
|
43
|
+
let auth = {};
|
|
44
|
+
if (fs.existsSync(authFile)) {
|
|
45
|
+
auth = JSON.parse(fs.readFileSync(authFile, 'utf-8'));
|
|
46
|
+
}
|
|
47
|
+
auth.OPENAI_API_KEY = preset.key;
|
|
48
|
+
fs.writeFileSync(authFile, JSON.stringify(auth, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Claude Code 配置
|
|
52
|
+
async function applyClaude(preset) {
|
|
53
|
+
if (isWindows) {
|
|
54
|
+
// Windows: 设置用户环境变量
|
|
55
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL', '${preset.url}', [System.EnvironmentVariableTarget]::User)"`);
|
|
56
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN', '${preset.key}', [System.EnvironmentVariableTarget]::User)"`);
|
|
57
|
+
} else {
|
|
58
|
+
// Linux/macOS: 修改 settings.json
|
|
59
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
60
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(claudeDir)) {
|
|
63
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let settings = {};
|
|
67
|
+
if (fs.existsSync(settingsFile)) {
|
|
68
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!settings.env) settings.env = {};
|
|
72
|
+
settings.env.ANTHROPIC_BASE_URL = preset.url;
|
|
73
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = preset.key;
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Gemini CLI 配置
|
|
80
|
+
async function applyGemini(preset) {
|
|
81
|
+
if (isWindows) {
|
|
82
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('GOOGLE_GEMINI_BASE_URL', '${preset.url}', [System.EnvironmentVariableTarget]::User)"`);
|
|
83
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('GEMINI_API_KEY', '${preset.key}', [System.EnvironmentVariableTarget]::User)"`);
|
|
84
|
+
} else {
|
|
85
|
+
// Linux/macOS: 添加到 shell 配置文件
|
|
86
|
+
const shellConfig = getShellConfigFile();
|
|
87
|
+
let content = '';
|
|
88
|
+
if (fs.existsSync(shellConfig)) {
|
|
89
|
+
content = fs.readFileSync(shellConfig, 'utf-8');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 移除旧的配置
|
|
93
|
+
content = content.replace(/^export GOOGLE_GEMINI_BASE_URL=.*$/gm, '');
|
|
94
|
+
content = content.replace(/^export GEMINI_API_KEY=.*$/gm, '');
|
|
95
|
+
content = content.trim();
|
|
96
|
+
|
|
97
|
+
// 添加新配置
|
|
98
|
+
content += `\nexport GOOGLE_GEMINI_BASE_URL="${preset.url}"\n`;
|
|
99
|
+
content += `export GEMINI_API_KEY="${preset.key}"\n`;
|
|
100
|
+
|
|
101
|
+
fs.writeFileSync(shellConfig, content);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 清除配置
|
|
106
|
+
async function clearCodex() {
|
|
107
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
108
|
+
const configFile = path.join(codexDir, 'config.toml');
|
|
109
|
+
const authFile = path.join(codexDir, 'auth.json');
|
|
110
|
+
|
|
111
|
+
if (fs.existsSync(configFile)) {
|
|
112
|
+
let config = parse(fs.readFileSync(configFile, 'utf-8'));
|
|
113
|
+
delete config.model_provider;
|
|
114
|
+
if (config.model_providers) delete config.model_providers.my;
|
|
115
|
+
fs.writeFileSync(configFile, stringify(config));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (fs.existsSync(authFile)) {
|
|
119
|
+
let auth = JSON.parse(fs.readFileSync(authFile, 'utf-8'));
|
|
120
|
+
delete auth.OPENAI_API_KEY;
|
|
121
|
+
fs.writeFileSync(authFile, JSON.stringify(auth, null, 2));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function clearClaude() {
|
|
126
|
+
if (isWindows) {
|
|
127
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL', $null, [System.EnvironmentVariableTarget]::User)"`);
|
|
128
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN', $null, [System.EnvironmentVariableTarget]::User)"`);
|
|
129
|
+
} else {
|
|
130
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
131
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
132
|
+
|
|
133
|
+
if (fs.existsSync(settingsFile)) {
|
|
134
|
+
let settings = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
|
|
135
|
+
if (settings.env) {
|
|
136
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
137
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
138
|
+
}
|
|
139
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function clearGemini() {
|
|
145
|
+
if (isWindows) {
|
|
146
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('GOOGLE_GEMINI_BASE_URL', $null, [System.EnvironmentVariableTarget]::User)"`);
|
|
147
|
+
await execAsync(`powershell -Command "[System.Environment]::SetEnvironmentVariable('GEMINI_API_KEY', $null, [System.EnvironmentVariableTarget]::User)"`);
|
|
148
|
+
} else {
|
|
149
|
+
const shellConfig = getShellConfigFile();
|
|
150
|
+
if (fs.existsSync(shellConfig)) {
|
|
151
|
+
let content = fs.readFileSync(shellConfig, 'utf-8');
|
|
152
|
+
content = content.replace(/^export GOOGLE_GEMINI_BASE_URL=.*$/gm, '');
|
|
153
|
+
content = content.replace(/^export GEMINI_API_KEY=.*$/gm, '');
|
|
154
|
+
fs.writeFileSync(shellConfig, content.trim() + '\n');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getShellConfigFile() {
|
|
160
|
+
const shell = process.env.SHELL || '/bin/bash';
|
|
161
|
+
if (shell.includes('zsh')) {
|
|
162
|
+
return path.join(os.homedir(), '.zshrc');
|
|
163
|
+
}
|
|
164
|
+
return path.join(os.homedir(), '.bashrc');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function applyConfig(tool, preset) {
|
|
168
|
+
const tools = tool === 'all' ? ['codex', 'claude', 'gemini'] : [tool];
|
|
169
|
+
|
|
170
|
+
for (const t of tools) {
|
|
171
|
+
switch (t) {
|
|
172
|
+
case 'codex':
|
|
173
|
+
await applyCodex(preset);
|
|
174
|
+
break;
|
|
175
|
+
case 'claude':
|
|
176
|
+
await applyClaude(preset);
|
|
177
|
+
break;
|
|
178
|
+
case 'gemini':
|
|
179
|
+
await applyGemini(preset);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function clearConfig(tool) {
|
|
186
|
+
const tools = tool === 'all' ? ['codex', 'claude', 'gemini'] : [tool];
|
|
187
|
+
|
|
188
|
+
for (const t of tools) {
|
|
189
|
+
switch (t) {
|
|
190
|
+
case 'codex':
|
|
191
|
+
await clearCodex();
|
|
192
|
+
break;
|
|
193
|
+
case 'claude':
|
|
194
|
+
await clearClaude();
|
|
195
|
+
break;
|
|
196
|
+
case 'gemini':
|
|
197
|
+
await clearGemini();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
package/lib/preset.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), '.code-cli');
|
|
7
|
+
const PRESETS_FILE = path.join(CONFIG_DIR, 'presets.json');
|
|
8
|
+
|
|
9
|
+
const TOOL_NAMES = { codex: 'Codex', claude: 'Claude Code', gemini: 'Gemini CLI' };
|
|
10
|
+
|
|
11
|
+
function ensureConfigDir() {
|
|
12
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
13
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function loadPresets() {
|
|
18
|
+
ensureConfigDir();
|
|
19
|
+
if (!fs.existsSync(PRESETS_FILE)) {
|
|
20
|
+
return { codex: [], claude: [], gemini: [] };
|
|
21
|
+
}
|
|
22
|
+
const data = JSON.parse(fs.readFileSync(PRESETS_FILE, 'utf-8'));
|
|
23
|
+
return { codex: data.codex || [], claude: data.claude || [], gemini: data.gemini || [] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function savePresets(presets) {
|
|
27
|
+
ensureConfigDir();
|
|
28
|
+
fs.writeFileSync(PRESETS_FILE, JSON.stringify(presets, null, 2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getPresets(tool) {
|
|
32
|
+
const presets = loadPresets();
|
|
33
|
+
return tool ? presets[tool] : presets;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function addPreset(tool, name, url, key) {
|
|
37
|
+
const presets = loadPresets();
|
|
38
|
+
const existing = presets[tool].findIndex(p => p.name === name);
|
|
39
|
+
|
|
40
|
+
if (existing >= 0) {
|
|
41
|
+
presets[tool][existing] = { name, url, key };
|
|
42
|
+
} else {
|
|
43
|
+
presets[tool].push({ name, url, key });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
savePresets(presets);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function deletePreset(tool, name) {
|
|
50
|
+
const presets = loadPresets();
|
|
51
|
+
presets[tool] = presets[tool].filter(p => p.name !== name);
|
|
52
|
+
savePresets(presets);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function listPresets(tool) {
|
|
56
|
+
const presets = loadPresets();
|
|
57
|
+
const tools = tool ? [tool] : ['codex', 'claude', 'gemini'];
|
|
58
|
+
|
|
59
|
+
let hasAny = false;
|
|
60
|
+
for (const t of tools) {
|
|
61
|
+
if (presets[t].length > 0) {
|
|
62
|
+
hasAny = true;
|
|
63
|
+
console.log(chalk.cyan(`\n${TOOL_NAMES[t]} 预设:`));
|
|
64
|
+
presets[t].forEach((p, i) => {
|
|
65
|
+
const maskedKey = p.key.length > 6 ? p.key.substring(0, 6) + '***' : '***';
|
|
66
|
+
console.log(` ${i + 1}. ${chalk.bold(p.name)}`);
|
|
67
|
+
console.log(` URL: ${p.url}`);
|
|
68
|
+
console.log(` Key: ${maskedKey}`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!hasAny) {
|
|
74
|
+
console.log(chalk.yellow('\n暂无预设'));
|
|
75
|
+
}
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liuzijian625/code-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI CLI 配置管理工具 - 管理 Codex、Claude Code、Gemini CLI 的配置",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"code-cli": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["cli", "codex", "claude", "gemini", "config"],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"inquirer": "^9.2.0",
|
|
18
|
+
"@iarna/toml": "^2.2.5",
|
|
19
|
+
"chalk": "^5.3.0"
|
|
20
|
+
}
|
|
21
|
+
}
|