claude-code-model-switch 1.0.2 → 2.1.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/README.md +18 -14
- package/README_CN.md +18 -14
- package/index.js +225 -144
- package/package.json +3 -3
- package/prompt.md +0 -45
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@ A CLI tool for switching between different AI models in Claude Code.
|
|
|
4
4
|
|
|
5
5
|
## 🚀 Features
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- Launch Claude Code with a preconfigured model in one command (yolo mode)
|
|
8
8
|
- Support for multiple pre-configured model environments
|
|
9
|
-
-
|
|
9
|
+
- `start` injects model config as env vars; `config` persists to settings.json
|
|
10
|
+
- Automatic handling of model-related environment variables and defaults
|
|
10
11
|
- Simple command-line interface
|
|
11
|
-
-
|
|
12
|
-
- Reset to official models with unset command
|
|
12
|
+
- `unset` removes model-related entries from settings.json
|
|
13
13
|
|
|
14
14
|
## 📦 Installation
|
|
15
15
|
|
|
@@ -22,11 +22,14 @@ npm install -g claude-code-model-switch
|
|
|
22
22
|
### Basic Commands
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
#
|
|
26
|
-
ccms deepseek-chat
|
|
25
|
+
# Launch Claude Code with the specified model (yolo mode enabled)
|
|
26
|
+
ccms start deepseek-chat
|
|
27
27
|
|
|
28
|
-
#
|
|
29
|
-
ccms
|
|
28
|
+
# Pass extra arguments directly to claude
|
|
29
|
+
ccms start deepseek-chat -p "hello"
|
|
30
|
+
|
|
31
|
+
# Write model configuration to ~/.claude/settings.json
|
|
32
|
+
ccms config deepseek-chat
|
|
30
33
|
|
|
31
34
|
# List all available models
|
|
32
35
|
ccms list
|
|
@@ -34,7 +37,7 @@ ccms list
|
|
|
34
37
|
# Show configuration file paths
|
|
35
38
|
ccms config-path
|
|
36
39
|
|
|
37
|
-
# Remove
|
|
40
|
+
# Remove model-related configuration from settings.json
|
|
38
41
|
ccms unset
|
|
39
42
|
```
|
|
40
43
|
|
|
@@ -86,18 +89,19 @@ Edit the configuration file `~/.claude-code-model-switch/settings.json`:
|
|
|
86
89
|
|
|
87
90
|
### Special Rules
|
|
88
91
|
|
|
89
|
-
1. **
|
|
90
|
-
2. **
|
|
92
|
+
1. **start command**: `ccms start <model>` injects model config as environment variables and launches `claude` with `--dangerously-skip-permissions` (yolo mode)
|
|
93
|
+
2. **config command**: `ccms config <model>` writes model config to `~/.claude/settings.json` for persistent configuration
|
|
94
|
+
3. **Default Values**:
|
|
91
95
|
- `CLAUDE_CODE_MAX_OUTPUT_TOKENS` defaults to `8192`
|
|
92
96
|
- `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` defaults to `1`
|
|
93
97
|
- Unset model fields automatically use the value of `ANTHROPIC_MODEL`
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
4. **Configuration File**: Empty configuration file `{"models": {}}` is automatically created on first run
|
|
99
|
+
5. **Unset Command**: `ccms unset` removes only model-specific variables from `settings.json` and restores official models
|
|
96
100
|
|
|
97
101
|
## 📁 File Locations
|
|
98
102
|
|
|
99
|
-
- Claude settings file: `~/.claude/settings.json`
|
|
100
103
|
- Model configuration file: `~/.claude-code-model-switch/settings.json`
|
|
104
|
+
- Claude Code settings file (used by `config` / `unset`): `~/.claude/settings.json`
|
|
101
105
|
|
|
102
106
|
## 🔧 Development
|
|
103
107
|
|
package/README_CN.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 🚀 功能特性
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 一条命令启动 Claude Code 并加载指定模型配置(yolo 模式)
|
|
8
8
|
- 支持多个预配置模型环境
|
|
9
|
-
-
|
|
9
|
+
- `start` 将模型配置注入环境变量,`config` 可持久化写入 settings.json
|
|
10
|
+
- 自动处理模型相关环境变量及默认值
|
|
10
11
|
- 命令行界面,操作简单
|
|
11
|
-
-
|
|
12
|
-
- 使用 unset 命令重置为官方模型
|
|
12
|
+
- `unset` 可清除 settings.json 中的模型相关配置
|
|
13
13
|
|
|
14
14
|
## 📦 安装
|
|
15
15
|
|
|
@@ -22,11 +22,14 @@ npm install -g claude-code-model-switch
|
|
|
22
22
|
### 基本命令
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
#
|
|
26
|
-
ccms deepseek-chat
|
|
25
|
+
# 用指定模型启动 Claude Code(自动启用 yolo 模式)
|
|
26
|
+
ccms start deepseek-chat
|
|
27
27
|
|
|
28
|
-
#
|
|
29
|
-
ccms
|
|
28
|
+
# 将额外参数直接传给 claude
|
|
29
|
+
ccms start deepseek-chat -p "hello"
|
|
30
|
+
|
|
31
|
+
# 把模型配置写入 ~/.claude/settings.json
|
|
32
|
+
ccms config deepseek-chat
|
|
30
33
|
|
|
31
34
|
# 列出所有可用模型
|
|
32
35
|
ccms list
|
|
@@ -34,7 +37,7 @@ ccms list
|
|
|
34
37
|
# 查看配置文件路径
|
|
35
38
|
ccms config-path
|
|
36
39
|
|
|
37
|
-
#
|
|
40
|
+
# 清除 settings.json 中的模型相关配置,恢复使用官方模型
|
|
38
41
|
ccms unset
|
|
39
42
|
```
|
|
40
43
|
|
|
@@ -86,18 +89,19 @@ ccms unset
|
|
|
86
89
|
|
|
87
90
|
### 特殊规则
|
|
88
91
|
|
|
89
|
-
1.
|
|
90
|
-
2.
|
|
92
|
+
1. **start 命令**:`ccms start <model>` 将模型配置注入环境变量并启动 `claude`,默认附带 `--dangerously-skip-permissions`(yolo 模式)
|
|
93
|
+
2. **config 命令**:`ccms config <model>` 将模型配置写入 `~/.claude/settings.json`,适合需要持久化配置的场景
|
|
94
|
+
3. **默认值处理**:
|
|
91
95
|
- `CLAUDE_CODE_MAX_OUTPUT_TOKENS` 默认为 `8192`
|
|
92
96
|
- `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 默认为 `1`
|
|
93
97
|
- 未设置的模型字段会自动使用 `ANTHROPIC_MODEL` 的值
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
4. **配置文件**:首次运行时会自动创建空的配置文件 `{"models": {}}`
|
|
99
|
+
5. **Unset 命令**:`ccms unset` 只删除 `settings.json` 中的模型相关变量,恢复使用官方模型
|
|
96
100
|
|
|
97
101
|
## 📁 文件位置
|
|
98
102
|
|
|
99
|
-
- Claude 设置文件:`~/.claude/settings.json`
|
|
100
103
|
- 模型配置文件:`~/.claude-code-model-switch/settings.json`
|
|
104
|
+
- Claude Code 设置文件(`config` / `unset` 使用):`~/.claude/settings.json`
|
|
101
105
|
|
|
102
106
|
## 🔧 开发
|
|
103
107
|
|
package/index.js
CHANGED
|
@@ -2,17 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
5
6
|
const { program } = require('commander');
|
|
6
7
|
|
|
7
|
-
// Configuration file paths
|
|
8
8
|
const CLAUDE_SETTINGS_PATH = path.join(process.env.HOME, '.claude', 'settings.json');
|
|
9
9
|
const MODEL_CONFIG_PATH = path.join(process.env.HOME, '.claude-code-model-switch', 'settings.json');
|
|
10
|
+
const CLAUDE_BIN = process.env.CLAUDE_BIN || 'claude';
|
|
11
|
+
const YOLO_ARGS = ['--dangerously-skip-permissions'];
|
|
10
12
|
|
|
11
|
-
// Default values
|
|
12
13
|
const DEFAULT_MAX_OUTPUT_TOKENS = '8192';
|
|
13
14
|
const DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC = '1';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
const MODEL_ENV_VARS = [
|
|
17
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
18
|
+
'ANTHROPIC_BASE_URL',
|
|
19
|
+
'ANTHROPIC_MODEL',
|
|
20
|
+
'ANTHROPIC_SMALL_FAST_MODEL',
|
|
21
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
22
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
23
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
24
|
+
'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
|
|
25
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
|
|
26
|
+
'CLAUDE_CODE_AUTO_COMPACT_WINDOW',
|
|
27
|
+
'API_TIMEOUT_MS'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const UNSET_ENV_VARS = [
|
|
31
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
32
|
+
'ANTHROPIC_BASE_URL',
|
|
33
|
+
'ANTHROPIC_MODEL',
|
|
34
|
+
'ANTHROPIC_SMALL_FAST_MODEL',
|
|
35
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
36
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
37
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const DERIVED_MODEL_FIELDS = [
|
|
41
|
+
'ANTHROPIC_SMALL_FAST_MODEL',
|
|
42
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
43
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
44
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const REQUIRED_ENV_VARS = [
|
|
48
|
+
'ANTHROPIC_MODEL',
|
|
49
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
50
|
+
'ANTHROPIC_BASE_URL'
|
|
51
|
+
];
|
|
52
|
+
|
|
16
53
|
function ensureConfigDir() {
|
|
17
54
|
const configDir = path.dirname(MODEL_CONFIG_PATH);
|
|
18
55
|
if (!fs.existsSync(configDir)) {
|
|
@@ -20,27 +57,23 @@ function ensureConfigDir() {
|
|
|
20
57
|
}
|
|
21
58
|
}
|
|
22
59
|
|
|
23
|
-
// Load model configuration
|
|
24
60
|
function loadModelConfig() {
|
|
25
61
|
ensureConfigDir();
|
|
26
62
|
|
|
27
63
|
if (!fs.existsSync(MODEL_CONFIG_PATH)) {
|
|
28
|
-
// Create empty default configuration file
|
|
29
64
|
const defaultConfig = { models: {} };
|
|
30
65
|
fs.writeFileSync(MODEL_CONFIG_PATH, JSON.stringify(defaultConfig, null, 2));
|
|
31
66
|
return defaultConfig;
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
try {
|
|
35
|
-
|
|
36
|
-
return JSON.parse(configContent);
|
|
70
|
+
return JSON.parse(fs.readFileSync(MODEL_CONFIG_PATH, 'utf8'));
|
|
37
71
|
} catch (error) {
|
|
38
72
|
console.error('Error: Failed to parse model configuration file');
|
|
39
73
|
process.exit(1);
|
|
40
74
|
}
|
|
41
75
|
}
|
|
42
76
|
|
|
43
|
-
// Load Claude settings
|
|
44
77
|
function loadClaudeSettings() {
|
|
45
78
|
if (!fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
46
79
|
console.error('Error: Claude settings file not found');
|
|
@@ -48,15 +81,13 @@ function loadClaudeSettings() {
|
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
try {
|
|
51
|
-
|
|
52
|
-
return JSON.parse(settingsContent);
|
|
84
|
+
return JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8'));
|
|
53
85
|
} catch (error) {
|
|
54
86
|
console.error('Error: Failed to parse Claude settings file');
|
|
55
87
|
process.exit(1);
|
|
56
88
|
}
|
|
57
89
|
}
|
|
58
90
|
|
|
59
|
-
// Save Claude settings
|
|
60
91
|
function saveClaudeSettings(settings) {
|
|
61
92
|
try {
|
|
62
93
|
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
@@ -66,20 +97,36 @@ function saveClaudeSettings(settings) {
|
|
|
66
97
|
}
|
|
67
98
|
}
|
|
68
99
|
|
|
69
|
-
|
|
100
|
+
function getValidModels(config = loadModelConfig()) {
|
|
101
|
+
return Object.keys(config.models || {}).filter(
|
|
102
|
+
(name) => config.models[name] && Object.keys(config.models[name]).length > 0
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function reportUnknownModel(model) {
|
|
107
|
+
const config = loadModelConfig();
|
|
108
|
+
const modelConfig = config.models && config.models[model];
|
|
109
|
+
|
|
110
|
+
if (modelConfig && Object.keys(modelConfig).length === 0) {
|
|
111
|
+
console.error(`Error: Model "${model}" configuration is empty`);
|
|
112
|
+
} else {
|
|
113
|
+
console.error(`Error: Model "${model}" not found`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('Available models:', getValidModels(config).join(', ') || 'None');
|
|
117
|
+
console.log('Run "ccms --help" to see available commands');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
70
121
|
function getModelConfig(modelName) {
|
|
71
122
|
const config = loadModelConfig();
|
|
72
123
|
|
|
73
124
|
if (!config.models || !config.models[modelName]) {
|
|
74
|
-
|
|
75
|
-
console.log('Available models:', Object.keys(config.models || {}).join(', ') || 'None');
|
|
76
|
-
console.log('Run "ccms --help" to see available commands');
|
|
77
|
-
process.exit(1);
|
|
125
|
+
reportUnknownModel(modelName);
|
|
78
126
|
}
|
|
79
127
|
|
|
80
128
|
const modelConfig = config.models[modelName];
|
|
81
129
|
|
|
82
|
-
// Check if model configuration is empty
|
|
83
130
|
if (Object.keys(modelConfig).length === 0) {
|
|
84
131
|
console.error(`Error: Model "${modelName}" configuration is empty`);
|
|
85
132
|
console.log('Run "ccms --help" to see available commands');
|
|
@@ -89,174 +136,162 @@ function getModelConfig(modelName) {
|
|
|
89
136
|
return modelConfig;
|
|
90
137
|
}
|
|
91
138
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const settings = loadClaudeSettings();
|
|
139
|
+
function normalizeModelConfig(modelConfig) {
|
|
140
|
+
const normalized = { ...modelConfig };
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
142
|
+
if (normalized.ANTHROPIC_MODEL) {
|
|
143
|
+
for (const field of DERIVED_MODEL_FIELDS) {
|
|
144
|
+
if (!normalized[field]) {
|
|
145
|
+
normalized[field] = normalized.ANTHROPIC_MODEL;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
99
148
|
}
|
|
100
149
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
// Apply configuration
|
|
114
|
-
for (const key of modelEnvVars) {
|
|
115
|
-
if (modelConfig[key] !== undefined) {
|
|
116
|
-
settings.env[key] = modelConfig[key];
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildModelEnv(modelConfig) {
|
|
154
|
+
const normalized = normalizeModelConfig(modelConfig);
|
|
155
|
+
const env = { ...process.env };
|
|
156
|
+
|
|
157
|
+
for (const key of MODEL_ENV_VARS) {
|
|
158
|
+
if (normalized[key] !== undefined) {
|
|
159
|
+
env[key] = String(normalized[key]);
|
|
117
160
|
}
|
|
118
161
|
}
|
|
119
162
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const requiredEnvVars = [
|
|
123
|
-
'ANTHROPIC_MODEL',
|
|
124
|
-
'ANTHROPIC_AUTH_TOKEN',
|
|
125
|
-
'ANTHROPIC_BASE_URL'
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
for (const envVar of requiredEnvVars) {
|
|
129
|
-
if (!settings.env[envVar]) {
|
|
163
|
+
for (const envVar of REQUIRED_ENV_VARS) {
|
|
164
|
+
if (!env[envVar]) {
|
|
130
165
|
console.error(`Error: ${envVar} is required`);
|
|
131
166
|
process.exit(1);
|
|
132
167
|
}
|
|
133
168
|
}
|
|
134
169
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
139
|
-
'ANTHROPIC_DEFAULT_OPUS_MODEL'
|
|
140
|
-
];
|
|
170
|
+
if (!env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
|
|
171
|
+
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
|
|
172
|
+
}
|
|
141
173
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
settings.env[field] = settings.env.ANTHROPIC_MODEL;
|
|
145
|
-
}
|
|
174
|
+
if (!env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
|
|
175
|
+
env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC;
|
|
146
176
|
}
|
|
147
177
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
178
|
+
return env;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getStartArgsFromArgv() {
|
|
182
|
+
const args = process.argv.slice(2);
|
|
183
|
+
|
|
184
|
+
if (args[0] !== 'start') {
|
|
185
|
+
return [];
|
|
151
186
|
}
|
|
152
187
|
|
|
153
|
-
|
|
154
|
-
|
|
188
|
+
let claudeArgs = args.slice(2);
|
|
189
|
+
if (claudeArgs[0] === '--') {
|
|
190
|
+
claudeArgs = claudeArgs.slice(1);
|
|
155
191
|
}
|
|
156
192
|
|
|
157
|
-
|
|
158
|
-
console.log(`✅ Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
|
|
193
|
+
return claudeArgs;
|
|
159
194
|
}
|
|
160
195
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
function hasPermissionOverride(args) {
|
|
197
|
+
return args.some((arg) =>
|
|
198
|
+
arg === '--dangerously-skip-permissions' ||
|
|
199
|
+
arg === '--allow-dangerously-skip-permissions' ||
|
|
200
|
+
arg.startsWith('--permission-mode')
|
|
201
|
+
);
|
|
202
|
+
}
|
|
165
203
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return;
|
|
204
|
+
function buildStartArgs(userArgs = []) {
|
|
205
|
+
if (hasPermissionOverride(userArgs)) {
|
|
206
|
+
return userArgs;
|
|
169
207
|
}
|
|
170
208
|
|
|
171
|
-
|
|
172
|
-
models.forEach(model => {
|
|
173
|
-
console.log(` - ${model}`);
|
|
174
|
-
});
|
|
209
|
+
return [...YOLO_ARGS, ...userArgs];
|
|
175
210
|
}
|
|
176
211
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Object.keys(config.models[modelName]).length > 0;
|
|
183
|
-
}
|
|
212
|
+
function launchClaude(env, claudeArgs = []) {
|
|
213
|
+
const child = spawn(CLAUDE_BIN, claudeArgs, {
|
|
214
|
+
env,
|
|
215
|
+
stdio: 'inherit'
|
|
216
|
+
});
|
|
184
217
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
.description('Claude Code Model Switch - Switch Claude Code models')
|
|
189
|
-
.version('1.0.2')
|
|
190
|
-
.argument('[model]', 'Model name to switch to')
|
|
191
|
-
.action((model) => {
|
|
192
|
-
if (model) {
|
|
193
|
-
// If model name argument provided, attempt to switch model
|
|
194
|
-
if (isValidModel(model)) {
|
|
195
|
-
const modelConfig = getModelConfig(model);
|
|
196
|
-
applyModelConfig(modelConfig);
|
|
197
|
-
} else {
|
|
198
|
-
const config = loadModelConfig();
|
|
199
|
-
const modelConfig = config.models && config.models[model];
|
|
200
|
-
|
|
201
|
-
if (modelConfig && Object.keys(modelConfig).length === 0) {
|
|
202
|
-
console.error(`Error: Model "${model}" configuration is empty`);
|
|
203
|
-
} else {
|
|
204
|
-
console.error(`Error: Model "${model}" not found`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const validModels = Object.keys(config.models || {}).filter(name =>
|
|
208
|
-
config.models[name] && Object.keys(config.models[name]).length > 0
|
|
209
|
-
);
|
|
210
|
-
console.log('Available models:', validModels.join(', ') || 'None');
|
|
211
|
-
console.log('Run "ccms --help" to see available commands');
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
218
|
+
child.on('error', (error) => {
|
|
219
|
+
if (error.code === 'ENOENT') {
|
|
220
|
+
console.error(`Error: "${CLAUDE_BIN}" command not found. Is Claude Code installed?`);
|
|
214
221
|
} else {
|
|
215
|
-
|
|
216
|
-
program.help();
|
|
222
|
+
console.error(`Error: Failed to start Claude Code (${error.message})`);
|
|
217
223
|
}
|
|
224
|
+
process.exit(1);
|
|
218
225
|
});
|
|
219
226
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
child.on('close', (code, signal) => {
|
|
228
|
+
if (signal) {
|
|
229
|
+
process.kill(process.pid, signal);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
process.exit(code ?? 0);
|
|
226
233
|
});
|
|
234
|
+
}
|
|
227
235
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
listModels();
|
|
233
|
-
});
|
|
236
|
+
function startClaudeWithModel(modelName) {
|
|
237
|
+
const modelConfig = getModelConfig(modelName);
|
|
238
|
+
const env = buildModelEnv(modelConfig);
|
|
239
|
+
const claudeArgs = buildStartArgs(getStartArgsFromArgv());
|
|
234
240
|
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
console.log(`[ccms] starting claude code with model: ${env.ANTHROPIC_MODEL} (yolo mode)\n`);
|
|
242
|
+
launchClaude(env, claudeArgs);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function applyModelConfig(modelConfig) {
|
|
237
246
|
const settings = loadClaudeSettings();
|
|
247
|
+
const normalized = normalizeModelConfig(modelConfig);
|
|
238
248
|
|
|
239
|
-
// Ensure env object exists
|
|
240
249
|
if (!settings.env) {
|
|
241
250
|
settings.env = {};
|
|
242
251
|
}
|
|
243
252
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
for (const key of MODEL_ENV_VARS) {
|
|
254
|
+
if (normalized[key] !== undefined) {
|
|
255
|
+
settings.env[key] = normalized[key];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const envVar of REQUIRED_ENV_VARS) {
|
|
260
|
+
if (!settings.env[envVar]) {
|
|
261
|
+
console.error(`Error: ${envVar} is required`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
|
|
267
|
+
settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
|
|
271
|
+
settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
saveClaudeSettings(settings);
|
|
275
|
+
console.log(`Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function configModel(modelName) {
|
|
279
|
+
const modelConfig = getModelConfig(modelName);
|
|
280
|
+
applyModelConfig(modelConfig);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function unsetModelConfig() {
|
|
284
|
+
const settings = loadClaudeSettings();
|
|
285
|
+
|
|
286
|
+
if (!settings.env) {
|
|
287
|
+
settings.env = {};
|
|
288
|
+
}
|
|
253
289
|
|
|
254
290
|
let removedCount = 0;
|
|
255
291
|
const removedVars = [];
|
|
256
292
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (settings.env.hasOwnProperty(key)) {
|
|
293
|
+
for (const key of UNSET_ENV_VARS) {
|
|
294
|
+
if (Object.prototype.hasOwnProperty.call(settings.env, key)) {
|
|
260
295
|
delete settings.env[key];
|
|
261
296
|
removedCount++;
|
|
262
297
|
removedVars.push(key);
|
|
@@ -273,6 +308,48 @@ function unsetModelConfig() {
|
|
|
273
308
|
}
|
|
274
309
|
}
|
|
275
310
|
|
|
311
|
+
function listModels() {
|
|
312
|
+
const models = getValidModels();
|
|
313
|
+
|
|
314
|
+
if (models.length === 0) {
|
|
315
|
+
console.log('No models configured');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log('Available models:');
|
|
320
|
+
models.forEach((model) => {
|
|
321
|
+
console.log(` - ${model}`);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
program
|
|
326
|
+
.name('ccms')
|
|
327
|
+
.description('Claude Code Model Switch - Manage and launch Claude Code with configured models')
|
|
328
|
+
.version('2.1.0');
|
|
329
|
+
|
|
330
|
+
program
|
|
331
|
+
.command('start <model> [claude-args...]')
|
|
332
|
+
.description('Launch Claude Code with model env vars (yolo mode enabled)')
|
|
333
|
+
.allowUnknownOption()
|
|
334
|
+
.allowExcessArguments()
|
|
335
|
+
.action((model) => {
|
|
336
|
+
startClaudeWithModel(model);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
program
|
|
340
|
+
.command('config <model>')
|
|
341
|
+
.description('Write model configuration to ~/.claude/settings.json')
|
|
342
|
+
.action((model) => {
|
|
343
|
+
configModel(model);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
program
|
|
347
|
+
.command('list')
|
|
348
|
+
.description('List all available models')
|
|
349
|
+
.action(() => {
|
|
350
|
+
listModels();
|
|
351
|
+
});
|
|
352
|
+
|
|
276
353
|
program
|
|
277
354
|
.command('config-path')
|
|
278
355
|
.description('Display configuration file paths')
|
|
@@ -283,9 +360,13 @@ program
|
|
|
283
360
|
|
|
284
361
|
program
|
|
285
362
|
.command('unset')
|
|
286
|
-
.description('Remove
|
|
363
|
+
.description('Remove model-related configuration from ~/.claude/settings.json')
|
|
287
364
|
.action(() => {
|
|
288
365
|
unsetModelConfig();
|
|
289
366
|
});
|
|
290
367
|
|
|
291
|
-
program.parse();
|
|
368
|
+
program.parse();
|
|
369
|
+
|
|
370
|
+
if (!process.argv.slice(2).length) {
|
|
371
|
+
program.help();
|
|
372
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-model-switch",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Launch Claude Code with preconfigured model environments",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ccms": "
|
|
7
|
+
"ccms": "index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
package/prompt.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
我需要实现一个claude code的模型切换器,这个切换器用node.js实现,可以通过npm安装.
|
|
2
|
-
|
|
3
|
-
实现逻辑非常简单.读取`~/.claude/settings.json`,切换里面和模型有关的环境变量。除了这些env,禁止修改配置文件的任何其他字段。
|
|
4
|
-
|
|
5
|
-
```json
|
|
6
|
-
{
|
|
7
|
-
"env": {
|
|
8
|
-
"ANTHROPIC_AUTH_TOKEN": "sk-caf695360e7f4b018bb1b308cbf992eb",
|
|
9
|
-
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
|
|
10
|
-
"ANTHROPIC_MODEL": "deepseek-chat",
|
|
11
|
-
"ANTHROPIC_SMALL_FAST_MODEL": "deepseek-chat",
|
|
12
|
-
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-chat",
|
|
13
|
-
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-chat",
|
|
14
|
-
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192",
|
|
15
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
这个工具本身通过~/.claude-code-models.json配置多个模型,格式为
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"models":{
|
|
25
|
-
"模型名称":{
|
|
26
|
-
"ANTHROPIC_AUTH_TOKEN": "sk-caf695360e7f4b018bb1b308cbf992eb",
|
|
27
|
-
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
|
|
28
|
-
"ANTHROPIC_MODEL": "deepseek-chat",
|
|
29
|
-
"ANTHROPIC_SMALL_FAST_MODEL": "deepseek-chat",
|
|
30
|
-
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-chat",
|
|
31
|
-
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-chat",
|
|
32
|
-
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192",
|
|
33
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
当用户执行`ccms 模型`命令时,读取`~/.claude-code-model-swich/settings.json`文件,检查是否有这个模型名称,没有报错,有就进行`~/.claude/settings.json`替换
|
|
40
|
-
|
|
41
|
-
重点:
|
|
42
|
-
1. `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`是一个特殊的环境变量,当用户没有设置这个环境变量时,**默认为1**,当用户设置为0时,表示允许非必要的流量,对应修改成0
|
|
43
|
-
2. `CLAUDE_CODE_MAX_OUTPUT_TOKENS`用户没有设置的时候,默认为8192
|
|
44
|
-
3. ANTHROPIC_SMALL_FAST_MODEL、ANTHROPIC_DEFAULT_SONNET_MODEL、ANTHROPIC_DEFAULT_OPUS_MODEL都可以不设置,只有ANTHROPIC_MODEL是必填的,其余三个环境变量在没有设置的时候,沿用ANTHROPIC_MODEL的值。
|
|
45
|
-
4. `~/.claude-code-model-swich/settings.json`文件不存在的时候,创建一个空的默认配置文件`{"models": {}}`,并按照模型不存在的错误提示用户(认为加载了这个空配置文件)
|