claude-code-model-switch 1.0.3 → 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 -148
- package/package.json +3 -3
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,178 +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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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;
|
|
142
|
+
if (normalized.ANTHROPIC_MODEL) {
|
|
143
|
+
for (const field of DERIVED_MODEL_FIELDS) {
|
|
144
|
+
if (!normalized[field]) {
|
|
145
|
+
normalized[field] = normalized.ANTHROPIC_MODEL;
|
|
113
146
|
}
|
|
114
147
|
}
|
|
115
148
|
}
|
|
116
149
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
];
|
|
128
|
-
|
|
129
|
-
// Apply configuration
|
|
130
|
-
for (const key of modelEnvVars) {
|
|
131
|
-
if (modelConfig[key] !== undefined) {
|
|
132
|
-
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]);
|
|
133
160
|
}
|
|
134
161
|
}
|
|
135
162
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const requiredEnvVars = [
|
|
139
|
-
'ANTHROPIC_MODEL',
|
|
140
|
-
'ANTHROPIC_AUTH_TOKEN',
|
|
141
|
-
'ANTHROPIC_BASE_URL'
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
for (const envVar of requiredEnvVars) {
|
|
145
|
-
if (!settings.env[envVar]) {
|
|
163
|
+
for (const envVar of REQUIRED_ENV_VARS) {
|
|
164
|
+
if (!env[envVar]) {
|
|
146
165
|
console.error(`Error: ${envVar} is required`);
|
|
147
166
|
process.exit(1);
|
|
148
167
|
}
|
|
149
168
|
}
|
|
150
169
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (!settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
|
|
154
|
-
settings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
|
|
170
|
+
if (!env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
|
|
171
|
+
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = DEFAULT_MAX_OUTPUT_TOKENS;
|
|
155
172
|
}
|
|
156
173
|
|
|
157
|
-
if (!
|
|
158
|
-
|
|
174
|
+
if (!env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
|
|
175
|
+
env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = DEFAULT_DISABLE_NONESSENTIAL_TRAFFIC;
|
|
159
176
|
}
|
|
160
177
|
|
|
161
|
-
|
|
162
|
-
console.log(`✅ Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
|
|
178
|
+
return env;
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const config = loadModelConfig();
|
|
168
|
-
const models = Object.keys(config.models || {});
|
|
181
|
+
function getStartArgsFromArgv() {
|
|
182
|
+
const args = process.argv.slice(2);
|
|
169
183
|
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
return;
|
|
184
|
+
if (args[0] !== 'start') {
|
|
185
|
+
return [];
|
|
173
186
|
}
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
188
|
+
let claudeArgs = args.slice(2);
|
|
189
|
+
if (claudeArgs[0] === '--') {
|
|
190
|
+
claudeArgs = claudeArgs.slice(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return claudeArgs;
|
|
179
194
|
}
|
|
180
195
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
+
);
|
|
187
202
|
}
|
|
188
203
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
}
|
|
204
|
+
function buildStartArgs(userArgs = []) {
|
|
205
|
+
if (hasPermissionOverride(userArgs)) {
|
|
206
|
+
return userArgs;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return [...YOLO_ARGS, ...userArgs];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function launchClaude(env, claudeArgs = []) {
|
|
213
|
+
const child = spawn(CLAUDE_BIN, claudeArgs, {
|
|
214
|
+
env,
|
|
215
|
+
stdio: 'inherit'
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
child.on('error', (error) => {
|
|
219
|
+
if (error.code === 'ENOENT') {
|
|
220
|
+
console.error(`Error: "${CLAUDE_BIN}" command not found. Is Claude Code installed?`);
|
|
218
221
|
} else {
|
|
219
|
-
|
|
220
|
-
program.help();
|
|
222
|
+
console.error(`Error: Failed to start Claude Code (${error.message})`);
|
|
221
223
|
}
|
|
224
|
+
process.exit(1);
|
|
222
225
|
});
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
child.on('close', (code, signal) => {
|
|
228
|
+
if (signal) {
|
|
229
|
+
process.kill(process.pid, signal);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
process.exit(code ?? 0);
|
|
230
233
|
});
|
|
234
|
+
}
|
|
231
235
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
listModels();
|
|
237
|
-
});
|
|
236
|
+
function startClaudeWithModel(modelName) {
|
|
237
|
+
const modelConfig = getModelConfig(modelName);
|
|
238
|
+
const env = buildModelEnv(modelConfig);
|
|
239
|
+
const claudeArgs = buildStartArgs(getStartArgsFromArgv());
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
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) {
|
|
241
246
|
const settings = loadClaudeSettings();
|
|
247
|
+
const normalized = normalizeModelConfig(modelConfig);
|
|
242
248
|
|
|
243
|
-
// Ensure env object exists
|
|
244
249
|
if (!settings.env) {
|
|
245
250
|
settings.env = {};
|
|
246
251
|
}
|
|
247
252
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
289
|
|
|
258
290
|
let removedCount = 0;
|
|
259
291
|
const removedVars = [];
|
|
260
292
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (settings.env.hasOwnProperty(key)) {
|
|
293
|
+
for (const key of UNSET_ENV_VARS) {
|
|
294
|
+
if (Object.prototype.hasOwnProperty.call(settings.env, key)) {
|
|
264
295
|
delete settings.env[key];
|
|
265
296
|
removedCount++;
|
|
266
297
|
removedVars.push(key);
|
|
@@ -277,6 +308,48 @@ function unsetModelConfig() {
|
|
|
277
308
|
}
|
|
278
309
|
}
|
|
279
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
|
+
|
|
280
353
|
program
|
|
281
354
|
.command('config-path')
|
|
282
355
|
.description('Display configuration file paths')
|
|
@@ -287,9 +360,13 @@ program
|
|
|
287
360
|
|
|
288
361
|
program
|
|
289
362
|
.command('unset')
|
|
290
|
-
.description('Remove
|
|
363
|
+
.description('Remove model-related configuration from ~/.claude/settings.json')
|
|
291
364
|
.action(() => {
|
|
292
365
|
unsetModelConfig();
|
|
293
366
|
});
|
|
294
367
|
|
|
295
|
-
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"
|