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 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
- - Quickly switch AI models used by Claude Code
7
+ - Launch Claude Code with a preconfigured model in one command (yolo mode)
8
8
  - Support for multiple pre-configured model environments
9
- - Automatic handling of model-related environment variables
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
- - Secure configuration management - only modifies model-related settings
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
- # Switch to specified model
26
- ccms deepseek-chat
25
+ # Launch Claude Code with the specified model (yolo mode enabled)
26
+ ccms start deepseek-chat
27
27
 
28
- # Or use switch subcommand
29
- ccms switch deepseek-chat
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 all model-related configuration and use official models
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. **Secure Configuration**: The tool only modifies model-related environment variables and won't touch other fields in the configuration file
90
- 2. **Default Values**:
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
- 3. **Configuration File**: Empty configuration file `{"models": {}}` is automatically created on first run
95
- 4. **Unset Command**: The `ccms unset` command removes only model-specific variables while preserving `CLAUDE_CODE_MAX_OUTPUT_TOKENS` and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`
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
- - 快速切换 Claude Code 使用的 AI 模型
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
- # 或者使用 switch 子命令
29
- ccms switch deepseek-chat
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
- 3. **配置文件**:首次运行时会自动创建空的配置文件 `{"models": {}}`
95
- 4. **Unset 命令**:`ccms unset` 命令只删除模型特定的变量,同时保留 `CLAUDE_CODE_MAX_OUTPUT_TOKENS` 和 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`
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
- // Ensure configuration directory exists
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
- const configContent = fs.readFileSync(MODEL_CONFIG_PATH, 'utf8');
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
- const settingsContent = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
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
- // Get model configuration
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
- console.error(`Error: Model "${modelName}" not found`);
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
- // Apply model configuration
93
- function applyModelConfig(modelConfig) {
94
- const settings = loadClaudeSettings();
139
+ function normalizeModelConfig(modelConfig) {
140
+ const normalized = { ...modelConfig };
95
141
 
96
- // Ensure env object exists
97
- if (!settings.env) {
98
- settings.env = {};
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
- // Only update model-related environment variables
102
- const modelEnvVars = [
103
- 'ANTHROPIC_AUTH_TOKEN',
104
- 'ANTHROPIC_BASE_URL',
105
- 'ANTHROPIC_MODEL',
106
- 'ANTHROPIC_SMALL_FAST_MODEL',
107
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
108
- 'ANTHROPIC_DEFAULT_OPUS_MODEL',
109
- 'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
110
- 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC'
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
- // Handle default value logic
121
- // Check required environment variables
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
- // If other model fields are not set, use ANTHROPIC_MODEL value
136
- const derivedModelFields = [
137
- 'ANTHROPIC_SMALL_FAST_MODEL',
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
- for (const field of derivedModelFields) {
143
- if (!settings.env[field]) {
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
- // Handle special environment variable defaults
149
- if (!settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
150
- settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
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
- if (!settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
154
- settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC;
188
+ let claudeArgs = args.slice(2);
189
+ if (claudeArgs[0] === '--') {
190
+ claudeArgs = claudeArgs.slice(1);
155
191
  }
156
192
 
157
- saveClaudeSettings(settings);
158
- console.log(`✅ Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
193
+ return claudeArgs;
159
194
  }
160
195
 
161
- // List all models
162
- function listModels() {
163
- const config = loadModelConfig();
164
- const models = Object.keys(config.models || {});
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
- if (models.length === 0) {
167
- console.log('No models configured');
168
- return;
204
+ function buildStartArgs(userArgs = []) {
205
+ if (hasPermissionOverride(userArgs)) {
206
+ return userArgs;
169
207
  }
170
208
 
171
- console.log('Available models:');
172
- models.forEach(model => {
173
- console.log(` - ${model}`);
174
- });
209
+ return [...YOLO_ARGS, ...userArgs];
175
210
  }
176
211
 
177
- // Check if model name is valid
178
- function isValidModel(modelName) {
179
- const config = loadModelConfig();
180
- return config.models &&
181
- config.models[modelName] &&
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
- // Main program
186
- program
187
- .name('ccms')
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
- // If no arguments provided, show help information
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
- program
221
- .command('switch <model>')
222
- .description('Switch to specified model')
223
- .action((model) => {
224
- const modelConfig = getModelConfig(model);
225
- applyModelConfig(modelConfig);
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
- program
229
- .command('list')
230
- .description('List all available models')
231
- .action(() => {
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
- // Unset all model-related configuration
236
- function unsetModelConfig() {
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
- // Model-related environment variables to remove (excluding some settings)
245
- const modelEnvVars = [
246
- 'ANTHROPIC_AUTH_TOKEN',
247
- 'ANTHROPIC_BASE_URL',
248
- 'ANTHROPIC_MODEL',
249
- 'ANTHROPIC_SMALL_FAST_MODEL',
250
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
251
- 'ANTHROPIC_DEFAULT_OPUS_MODEL'
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
- // Remove only existing model-related environment variables
258
- for (const key of modelEnvVars) {
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 all model-related configuration and use official models')
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.2",
4
- "description": "A CLI tool to switch Claude Code models",
3
+ "version": "2.1.0",
4
+ "description": "Launch Claude Code with preconfigured model environments",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "ccms": "./index.js"
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": {}}`,并按照模型不存在的错误提示用户(认为加载了这个空配置文件)