claude-code-model-switch 1.0.3 → 2.2.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,13 @@ 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
+ - `shared` env vars apply to all models (model-specific values override shared)
11
+ - Automatic handling of model-related environment variables and defaults
10
12
  - Simple command-line interface
11
- - Secure configuration management - only modifies model-related settings
12
- - Reset to official models with unset command
13
+ - `unset` removes model-related entries from settings.json
13
14
 
14
15
  ## 📦 Installation
15
16
 
@@ -22,11 +23,14 @@ npm install -g claude-code-model-switch
22
23
  ### Basic Commands
23
24
 
24
25
  ```bash
25
- # Switch to specified model
26
- ccms deepseek-chat
26
+ # Launch Claude Code with the specified model (yolo mode enabled)
27
+ ccms start deepseek-chat
27
28
 
28
- # Or use switch subcommand
29
- ccms switch deepseek-chat
29
+ # Pass extra arguments directly to claude
30
+ ccms start deepseek-chat -p "hello"
31
+
32
+ # Write model configuration to ~/.claude/settings.json
33
+ ccms config deepseek-chat
30
34
 
31
35
  # List all available models
32
36
  ccms list
@@ -34,8 +38,13 @@ ccms list
34
38
  # Show configuration file paths
35
39
  ccms config-path
36
40
 
37
- # Remove all model-related configuration and use official models
41
+ # Remove model-related configuration from settings.json
38
42
  ccms unset
43
+
44
+ # Manage shared environment variables for all models
45
+ ccms shared list
46
+ ccms shared set API_TIMEOUT_MS 600000
47
+ ccms shared unset API_TIMEOUT_MS
39
48
  ```
40
49
 
41
50
  ### Model Configuration
@@ -44,6 +53,10 @@ Edit the configuration file `~/.claude-code-model-switch/settings.json`:
44
53
 
45
54
  ```json
46
55
  {
56
+ "shared": {
57
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
58
+ "API_TIMEOUT_MS": "600000"
59
+ },
47
60
  "models": {
48
61
  "deepseek-chat": {
49
62
  "ANTHROPIC_AUTH_TOKEN": "sk-your-token-here",
@@ -86,18 +99,20 @@ Edit the configuration file `~/.claude-code-model-switch/settings.json`:
86
99
 
87
100
  ### Special Rules
88
101
 
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**:
102
+ 1. **start command**: `ccms start <model>` injects model config as environment variables and launches `claude` with `--dangerously-skip-permissions` (yolo mode)
103
+ 2. **config command**: `ccms config <model>` writes model config to `~/.claude/settings.json` for persistent configuration
104
+ 3. **shared env vars**: variables in `shared` apply to every model; model-specific values override shared ones
105
+ 4. **Default Values**:
91
106
  - `CLAUDE_CODE_MAX_OUTPUT_TOKENS` defaults to `8192`
92
107
  - `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` defaults to `1`
93
108
  - 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`
109
+ 4. **Configuration File**: Empty configuration file `{"shared": {}, "models": {}}` is automatically created on first run
110
+ 5. **Unset Command**: `ccms unset` removes only model-specific variables from `settings.json` and restores official models
96
111
 
97
112
  ## 📁 File Locations
98
113
 
99
- - Claude settings file: `~/.claude/settings.json`
100
114
  - Model configuration file: `~/.claude-code-model-switch/settings.json`
115
+ - Claude Code settings file (used by `config` / `unset`): `~/.claude/settings.json`
101
116
 
102
117
  ## 🔧 Development
103
118
 
package/README_CN.md CHANGED
@@ -4,12 +4,13 @@
4
4
 
5
5
  ## 🚀 功能特性
6
6
 
7
- - 快速切换 Claude Code 使用的 AI 模型
7
+ - 一条命令启动 Claude Code 并加载指定模型配置(yolo 模式)
8
8
  - 支持多个预配置模型环境
9
- - 自动处理模型相关环境变量
9
+ - `start` 将模型配置注入环境变量,`config` 可持久化写入 settings.json
10
+ - 支持 `shared` 公共环境变量,所有模型自动继承(模型配置可覆盖)
11
+ - 自动处理模型相关环境变量及默认值
10
12
  - 命令行界面,操作简单
11
- - 安全的配置管理,只修改模型相关设置
12
- - 使用 unset 命令重置为官方模型
13
+ - `unset` 可清除 settings.json 中的模型相关配置
13
14
 
14
15
  ## 📦 安装
15
16
 
@@ -22,11 +23,14 @@ npm install -g claude-code-model-switch
22
23
  ### 基本命令
23
24
 
24
25
  ```bash
25
- # 切换到指定模型
26
- ccms deepseek-chat
26
+ # 用指定模型启动 Claude Code(自动启用 yolo 模式)
27
+ ccms start deepseek-chat
27
28
 
28
- # 或者使用 switch 子命令
29
- ccms switch deepseek-chat
29
+ # 将额外参数直接传给 claude
30
+ ccms start deepseek-chat -p "hello"
31
+
32
+ # 把模型配置写入 ~/.claude/settings.json
33
+ ccms config deepseek-chat
30
34
 
31
35
  # 列出所有可用模型
32
36
  ccms list
@@ -34,8 +38,13 @@ ccms list
34
38
  # 查看配置文件路径
35
39
  ccms config-path
36
40
 
37
- # 清除所有模型相关配置,恢复使用官方模型
41
+ # 清除 settings.json 中的模型相关配置,恢复使用官方模型
38
42
  ccms unset
43
+
44
+ # 管理所有模型共享的环境变量
45
+ ccms shared list
46
+ ccms shared set API_TIMEOUT_MS 600000
47
+ ccms shared unset API_TIMEOUT_MS
39
48
  ```
40
49
 
41
50
  ### 配置模型
@@ -44,6 +53,10 @@ ccms unset
44
53
 
45
54
  ```json
46
55
  {
56
+ "shared": {
57
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
58
+ "API_TIMEOUT_MS": "600000"
59
+ },
47
60
  "models": {
48
61
  "deepseek-chat": {
49
62
  "ANTHROPIC_AUTH_TOKEN": "sk-your-token-here",
@@ -86,18 +99,20 @@ ccms unset
86
99
 
87
100
  ### 特殊规则
88
101
 
89
- 1. **安全配置**:工具只会修改与模型相关的环境变量,不会触及配置文件中的其他字段
90
- 2. **默认值处理**:
102
+ 1. **start 命令**:`ccms start <model>` 将模型配置注入环境变量并启动 `claude`,默认附带 `--dangerously-skip-permissions`(yolo 模式)
103
+ 2. **config 命令**:`ccms config <model>` 将模型配置写入 `~/.claude/settings.json`,适合需要持久化配置的场景
104
+ 3. **shared 公共变量**:`shared` 中的环境变量会应用到所有模型;单个模型里同名变量会覆盖 shared
105
+ 4. **默认值处理**:
91
106
  - `CLAUDE_CODE_MAX_OUTPUT_TOKENS` 默认为 `8192`
92
107
  - `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 默认为 `1`
93
108
  - 未设置的模型字段会自动使用 `ANTHROPIC_MODEL` 的值
94
- 3. **配置文件**:首次运行时会自动创建空的配置文件 `{"models": {}}`
95
- 4. **Unset 命令**:`ccms unset` 命令只删除模型特定的变量,同时保留 `CLAUDE_CODE_MAX_OUTPUT_TOKENS` 和 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`
109
+ 4. **配置文件**:首次运行时会自动创建空的配置文件 `{"shared": {}, "models": {}}`
110
+ 5. **Unset 命令**:`ccms unset` 只删除 `settings.json` 中的模型相关变量,恢复使用官方模型
96
111
 
97
112
  ## 📁 文件位置
98
113
 
99
- - Claude 设置文件:`~/.claude/settings.json`
100
114
  - 模型配置文件:`~/.claude-code-model-switch/settings.json`
115
+ - Claude Code 设置文件(`config` / `unset` 使用):`~/.claude/settings.json`
101
116
 
102
117
  ## 🔧 开发
103
118
 
@@ -1,4 +1,8 @@
1
1
  {
2
+ "shared": {
3
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
4
+ "API_TIMEOUT_MS": "600000"
5
+ },
2
6
  "models": {
3
7
  "deepseek-chat": {
4
8
  "ANTHROPIC_AUTH_TOKEN": "sk-caf695360e7f4b018bb1b308cbf992eb",
@@ -7,8 +11,7 @@
7
11
  "ANTHROPIC_SMALL_FAST_MODEL": "deepseek-chat",
8
12
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-chat",
9
13
  "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-chat",
10
- "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192",
11
- "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
14
+ "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192"
12
15
  },
13
16
  "claude-3-5-sonnet": {
14
17
  "ANTHROPIC_AUTH_TOKEN": "your-anthropic-token-here",
@@ -21,4 +24,4 @@
21
24
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "0"
22
25
  }
23
26
  }
24
- }
27
+ }
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
- const defaultConfig = { models: {} };
64
+ const defaultConfig = { shared: {}, 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,52 @@ function saveClaudeSettings(settings) {
66
97
  }
67
98
  }
68
99
 
69
- // Get model configuration
70
- function getModelConfig(modelName) {
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) {
71
107
  const config = loadModelConfig();
108
+ const modelConfig = config.models && config.models[model];
72
109
 
73
- 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');
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
+
121
+ function saveModelConfig(config) {
122
+ ensureConfigDir();
123
+ try {
124
+ fs.writeFileSync(MODEL_CONFIG_PATH, JSON.stringify(config, null, 2));
125
+ } catch (error) {
126
+ console.error('Error: Failed to save model configuration file');
77
127
  process.exit(1);
78
128
  }
129
+ }
130
+
131
+ function getSharedEnv(config = loadModelConfig()) {
132
+ const shared = config.shared;
133
+ if (!shared || typeof shared !== 'object' || Array.isArray(shared)) {
134
+ return {};
135
+ }
136
+ return shared;
137
+ }
138
+
139
+ function getRawModelConfig(modelName, config = loadModelConfig()) {
140
+ if (!config.models || !config.models[modelName]) {
141
+ reportUnknownModel(modelName);
142
+ }
79
143
 
80
144
  const modelConfig = config.models[modelName];
81
145
 
82
- // Check if model configuration is empty
83
146
  if (Object.keys(modelConfig).length === 0) {
84
147
  console.error(`Error: Model "${modelName}" configuration is empty`);
85
148
  console.log('Run "ccms --help" to see available commands');
@@ -89,67 +152,141 @@ function getModelConfig(modelName) {
89
152
  return modelConfig;
90
153
  }
91
154
 
92
- // Apply model configuration
93
- function applyModelConfig(modelConfig) {
94
- const settings = loadClaudeSettings();
155
+ function mergeModelConfig(modelName) {
156
+ const config = loadModelConfig();
157
+ const shared = getSharedEnv(config);
158
+ const modelConfig = getRawModelConfig(modelName, config);
95
159
 
96
- // Ensure env object exists
97
- if (!settings.env) {
98
- settings.env = {};
99
- }
160
+ return { ...shared, ...modelConfig };
161
+ }
162
+
163
+ function normalizeModelConfig(modelConfig) {
164
+ const normalized = { ...modelConfig };
100
165
 
101
- // First, set default values for unset model fields in the config itself
102
- // If ANTHROPIC_MODEL is set but other model fields are not, use ANTHROPIC_MODEL value
103
- if (modelConfig.ANTHROPIC_MODEL) {
104
- const derivedModelFields = [
105
- 'ANTHROPIC_SMALL_FAST_MODEL',
106
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
107
- 'ANTHROPIC_DEFAULT_OPUS_MODEL'
108
- ];
109
-
110
- for (const field of derivedModelFields) {
111
- if (!modelConfig[field]) {
112
- modelConfig[field] = modelConfig.ANTHROPIC_MODEL;
166
+ if (normalized.ANTHROPIC_MODEL) {
167
+ for (const field of DERIVED_MODEL_FIELDS) {
168
+ if (!normalized[field]) {
169
+ normalized[field] = normalized.ANTHROPIC_MODEL;
113
170
  }
114
171
  }
115
172
  }
116
173
 
117
- // Only update model-related environment variables
118
- const modelEnvVars = [
119
- 'ANTHROPIC_AUTH_TOKEN',
120
- 'ANTHROPIC_BASE_URL',
121
- 'ANTHROPIC_MODEL',
122
- 'ANTHROPIC_SMALL_FAST_MODEL',
123
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
124
- 'ANTHROPIC_DEFAULT_OPUS_MODEL',
125
- 'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
126
- 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC'
127
- ];
128
-
129
- // Apply configuration
130
- for (const key of modelEnvVars) {
131
- if (modelConfig[key] !== undefined) {
132
- settings.env[key] = modelConfig[key];
174
+ return normalized;
175
+ }
176
+
177
+ function buildModelEnv(modelConfig) {
178
+ const normalized = normalizeModelConfig(modelConfig);
179
+ const env = { ...process.env };
180
+
181
+ for (const [key, value] of Object.entries(normalized)) {
182
+ if (value !== undefined && value !== null) {
183
+ env[key] = String(value);
184
+ }
185
+ }
186
+
187
+ for (const envVar of REQUIRED_ENV_VARS) {
188
+ if (!env[envVar]) {
189
+ console.error(`Error: ${envVar} is required`);
190
+ process.exit(1);
133
191
  }
134
192
  }
135
193
 
136
- // Handle default value logic
137
- // Check required environment variables
138
- const requiredEnvVars = [
139
- 'ANTHROPIC_MODEL',
140
- 'ANTHROPIC_AUTH_TOKEN',
141
- 'ANTHROPIC_BASE_URL'
142
- ];
194
+ if (!env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
195
+ env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
196
+ }
143
197
 
144
- for (const envVar of requiredEnvVars) {
198
+ if (!env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
199
+ env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC;
200
+ }
201
+
202
+ return env;
203
+ }
204
+
205
+ function getStartArgsFromArgv() {
206
+ const args = process.argv.slice(2);
207
+
208
+ if (args[0] !== 'start') {
209
+ return [];
210
+ }
211
+
212
+ let claudeArgs = args.slice(2);
213
+ if (claudeArgs[0] === '--') {
214
+ claudeArgs = claudeArgs.slice(1);
215
+ }
216
+
217
+ return claudeArgs;
218
+ }
219
+
220
+ function hasPermissionOverride(args) {
221
+ return args.some((arg) =>
222
+ arg === '--dangerously-skip-permissions' ||
223
+ arg === '--allow-dangerously-skip-permissions' ||
224
+ arg.startsWith('--permission-mode')
225
+ );
226
+ }
227
+
228
+ function buildStartArgs(userArgs = []) {
229
+ if (hasPermissionOverride(userArgs)) {
230
+ return userArgs;
231
+ }
232
+
233
+ return [...YOLO_ARGS, ...userArgs];
234
+ }
235
+
236
+ function launchClaude(env, claudeArgs = []) {
237
+ const child = spawn(CLAUDE_BIN, claudeArgs, {
238
+ env,
239
+ stdio: 'inherit'
240
+ });
241
+
242
+ child.on('error', (error) => {
243
+ if (error.code === 'ENOENT') {
244
+ console.error(`Error: "${CLAUDE_BIN}" command not found. Is Claude Code installed?`);
245
+ } else {
246
+ console.error(`Error: Failed to start Claude Code (${error.message})`);
247
+ }
248
+ process.exit(1);
249
+ });
250
+
251
+ child.on('close', (code, signal) => {
252
+ if (signal) {
253
+ process.kill(process.pid, signal);
254
+ return;
255
+ }
256
+ process.exit(code ?? 0);
257
+ });
258
+ }
259
+
260
+ function startClaudeWithModel(modelName) {
261
+ const modelConfig = mergeModelConfig(modelName);
262
+ const env = buildModelEnv(modelConfig);
263
+ const claudeArgs = buildStartArgs(getStartArgsFromArgv());
264
+
265
+ console.log(`[ccms] starting claude code with model: ${env.ANTHROPIC_MODEL} (yolo mode)\n`);
266
+ launchClaude(env, claudeArgs);
267
+ }
268
+
269
+ function applyModelConfig(modelConfig) {
270
+ const settings = loadClaudeSettings();
271
+ const normalized = normalizeModelConfig(modelConfig);
272
+
273
+ if (!settings.env) {
274
+ settings.env = {};
275
+ }
276
+
277
+ for (const [key, value] of Object.entries(normalized)) {
278
+ if (value !== undefined && value !== null) {
279
+ settings.env[key] = String(value);
280
+ }
281
+ }
282
+
283
+ for (const envVar of REQUIRED_ENV_VARS) {
145
284
  if (!settings.env[envVar]) {
146
285
  console.error(`Error: ${envVar} is required`);
147
286
  process.exit(1);
148
287
  }
149
288
  }
150
289
 
151
-
152
- // Handle special environment variable defaults
153
290
  if (!settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
154
291
  settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
155
292
  }
@@ -159,108 +296,67 @@ function applyModelConfig(modelConfig) {
159
296
  }
160
297
 
161
298
  saveClaudeSettings(settings);
162
- console.log(`✅ Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
299
+ console.log(`Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
163
300
  }
164
301
 
165
- // List all models
166
- function listModels() {
167
- const config = loadModelConfig();
168
- const models = Object.keys(config.models || {});
302
+ function configModel(modelName) {
303
+ const modelConfig = mergeModelConfig(modelName);
304
+ applyModelConfig(modelConfig);
305
+ }
169
306
 
170
- if (models.length === 0) {
171
- console.log('No models configured');
307
+ function listSharedEnv() {
308
+ const shared = getSharedEnv();
309
+ const keys = Object.keys(shared);
310
+
311
+ if (keys.length === 0) {
312
+ console.log('No shared environment variables configured');
172
313
  return;
173
314
  }
174
315
 
175
- console.log('Available models:');
176
- models.forEach(model => {
177
- console.log(` - ${model}`);
316
+ console.log('Shared environment variables:');
317
+ keys.forEach((key) => {
318
+ console.log(` ${key}=${shared[key]}`);
178
319
  });
179
320
  }
180
321
 
181
- // Check if model name is valid
182
- function isValidModel(modelName) {
322
+ function setSharedEnv(key, value) {
183
323
  const config = loadModelConfig();
184
- return config.models &&
185
- config.models[modelName] &&
186
- Object.keys(config.models[modelName]).length > 0;
324
+
325
+ if (!config.shared || typeof config.shared !== 'object' || Array.isArray(config.shared)) {
326
+ config.shared = {};
327
+ }
328
+
329
+ config.shared[key] = value;
330
+ saveModelConfig(config);
331
+ console.log(`[ccms] shared env set: ${key}=${value}`);
187
332
  }
188
333
 
189
- // Main program
190
- program
191
- .name('ccms')
192
- .description('Claude Code Model Switch - Switch Claude Code models')
193
- .version('1.0.3')
194
- .argument('[model]', 'Model name to switch to')
195
- .action((model) => {
196
- if (model) {
197
- // If model name argument provided, attempt to switch model
198
- if (isValidModel(model)) {
199
- const modelConfig = getModelConfig(model);
200
- applyModelConfig(modelConfig);
201
- } else {
202
- const config = loadModelConfig();
203
- const modelConfig = config.models && config.models[model];
204
-
205
- if (modelConfig && Object.keys(modelConfig).length === 0) {
206
- console.error(`Error: Model "${model}" configuration is empty`);
207
- } else {
208
- console.error(`Error: Model "${model}" not found`);
209
- }
210
-
211
- const validModels = Object.keys(config.models || {}).filter(name =>
212
- config.models[name] && Object.keys(config.models[name]).length > 0
213
- );
214
- console.log('Available models:', validModels.join(', ') || 'None');
215
- console.log('Run "ccms --help" to see available commands');
216
- process.exit(1);
217
- }
218
- } else {
219
- // If no arguments provided, show help information
220
- program.help();
221
- }
222
- });
334
+ function unsetSharedEnv(key) {
335
+ const config = loadModelConfig();
336
+ const shared = getSharedEnv(config);
223
337
 
224
- program
225
- .command('switch <model>')
226
- .description('Switch to specified model')
227
- .action((model) => {
228
- const modelConfig = getModelConfig(model);
229
- applyModelConfig(modelConfig);
230
- });
338
+ if (!Object.prototype.hasOwnProperty.call(shared, key)) {
339
+ console.log(`No shared environment variable found: ${key}`);
340
+ return;
341
+ }
231
342
 
232
- program
233
- .command('list')
234
- .description('List all available models')
235
- .action(() => {
236
- listModels();
237
- });
343
+ delete config.shared[key];
344
+ saveModelConfig(config);
345
+ console.log(`[ccms] shared env removed: ${key}`);
346
+ }
238
347
 
239
- // Unset all model-related configuration
240
348
  function unsetModelConfig() {
241
349
  const settings = loadClaudeSettings();
242
350
 
243
- // Ensure env object exists
244
351
  if (!settings.env) {
245
352
  settings.env = {};
246
353
  }
247
354
 
248
- // Model-related environment variables to remove (excluding some settings)
249
- const modelEnvVars = [
250
- 'ANTHROPIC_AUTH_TOKEN',
251
- 'ANTHROPIC_BASE_URL',
252
- 'ANTHROPIC_MODEL',
253
- 'ANTHROPIC_SMALL_FAST_MODEL',
254
- 'ANTHROPIC_DEFAULT_SONNET_MODEL',
255
- 'ANTHROPIC_DEFAULT_OPUS_MODEL'
256
- ];
257
-
258
355
  let removedCount = 0;
259
356
  const removedVars = [];
260
357
 
261
- // Remove only existing model-related environment variables
262
- for (const key of modelEnvVars) {
263
- if (settings.env.hasOwnProperty(key)) {
358
+ for (const key of UNSET_ENV_VARS) {
359
+ if (Object.prototype.hasOwnProperty.call(settings.env, key)) {
264
360
  delete settings.env[key];
265
361
  removedCount++;
266
362
  removedVars.push(key);
@@ -277,6 +373,48 @@ function unsetModelConfig() {
277
373
  }
278
374
  }
279
375
 
376
+ function listModels() {
377
+ const models = getValidModels();
378
+
379
+ if (models.length === 0) {
380
+ console.log('No models configured');
381
+ return;
382
+ }
383
+
384
+ console.log('Available models:');
385
+ models.forEach((model) => {
386
+ console.log(` - ${model}`);
387
+ });
388
+ }
389
+
390
+ program
391
+ .name('ccms')
392
+ .description('Claude Code Model Switch - Manage and launch Claude Code with configured models')
393
+ .version('2.2.0');
394
+
395
+ program
396
+ .command('start <model> [claude-args...]')
397
+ .description('Launch Claude Code with model env vars (yolo mode enabled)')
398
+ .allowUnknownOption()
399
+ .allowExcessArguments()
400
+ .action((model) => {
401
+ startClaudeWithModel(model);
402
+ });
403
+
404
+ program
405
+ .command('config <model>')
406
+ .description('Write model configuration to ~/.claude/settings.json')
407
+ .action((model) => {
408
+ configModel(model);
409
+ });
410
+
411
+ program
412
+ .command('list')
413
+ .description('List all available models')
414
+ .action(() => {
415
+ listModels();
416
+ });
417
+
280
418
  program
281
419
  .command('config-path')
282
420
  .description('Display configuration file paths')
@@ -287,9 +425,48 @@ program
287
425
 
288
426
  program
289
427
  .command('unset')
290
- .description('Remove all model-related configuration and use official models')
428
+ .description('Remove model-related configuration from ~/.claude/settings.json')
291
429
  .action(() => {
292
430
  unsetModelConfig();
293
431
  });
294
432
 
295
- program.parse();
433
+ program
434
+ .command('shared')
435
+ .description('Manage shared environment variables for all models')
436
+ .argument('[action]', 'list, set, or unset')
437
+ .argument('[key]', 'Environment variable name')
438
+ .argument('[value]', 'Environment variable value')
439
+ .action((action, key, value) => {
440
+ if (!action || action === 'list') {
441
+ listSharedEnv();
442
+ return;
443
+ }
444
+
445
+ if (action === 'set') {
446
+ if (!key || value === undefined) {
447
+ console.error('Usage: ccms shared set <key> <value>');
448
+ process.exit(1);
449
+ }
450
+ setSharedEnv(key, value);
451
+ return;
452
+ }
453
+
454
+ if (action === 'unset') {
455
+ if (!key) {
456
+ console.error('Usage: ccms shared unset <key>');
457
+ process.exit(1);
458
+ }
459
+ unsetSharedEnv(key);
460
+ return;
461
+ }
462
+
463
+ console.error(`Unknown shared action: ${action}`);
464
+ console.log('Usage: ccms shared [list|set <key> <value>|unset <key>]');
465
+ process.exit(1);
466
+ });
467
+
468
+ program.parse();
469
+
470
+ if (!process.argv.slice(2).length) {
471
+ program.help();
472
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "claude-code-model-switch",
3
- "version": "1.0.3",
4
- "description": "A CLI tool to switch Claude Code models",
3
+ "version": "2.2.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"