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 +29 -14
- package/README_CN.md +29 -14
- package/example-config.json +6 -3
- package/index.js +320 -143
- package/package.json +3 -3
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
|
-
-
|
|
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
|
+
- `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
|
-
-
|
|
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
|
-
#
|
|
26
|
-
ccms deepseek-chat
|
|
26
|
+
# Launch Claude Code with the specified model (yolo mode enabled)
|
|
27
|
+
ccms start deepseek-chat
|
|
27
28
|
|
|
28
|
-
#
|
|
29
|
-
ccms
|
|
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
|
|
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. **
|
|
90
|
-
2. **
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
-
|
|
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
|
-
#
|
|
29
|
-
ccms
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
package/example-config.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,52 @@ function saveClaudeSettings(settings) {
|
|
|
66
97
|
}
|
|
67
98
|
}
|
|
68
99
|
|
|
69
|
-
|
|
70
|
-
|
|
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 (
|
|
74
|
-
console.error(`Error: Model "${
|
|
75
|
-
|
|
76
|
-
console.
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
155
|
+
function mergeModelConfig(modelName) {
|
|
156
|
+
const config = loadModelConfig();
|
|
157
|
+
const shared = getSharedEnv(config);
|
|
158
|
+
const modelConfig = getRawModelConfig(modelName, config);
|
|
95
159
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
160
|
+
return { ...shared, ...modelConfig };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeModelConfig(modelConfig) {
|
|
164
|
+
const normalized = { ...modelConfig };
|
|
100
165
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const
|
|
131
|
-
if (
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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(
|
|
299
|
+
console.log(`Switched to model: ${settings.env.ANTHROPIC_MODEL}`);
|
|
163
300
|
}
|
|
164
301
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
302
|
+
function configModel(modelName) {
|
|
303
|
+
const modelConfig = mergeModelConfig(modelName);
|
|
304
|
+
applyModelConfig(modelConfig);
|
|
305
|
+
}
|
|
169
306
|
|
|
170
|
-
|
|
171
|
-
|
|
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('
|
|
176
|
-
|
|
177
|
-
console.log(`
|
|
316
|
+
console.log('Shared environment variables:');
|
|
317
|
+
keys.forEach((key) => {
|
|
318
|
+
console.log(` ${key}=${shared[key]}`);
|
|
178
319
|
});
|
|
179
320
|
}
|
|
180
321
|
|
|
181
|
-
|
|
182
|
-
function isValidModel(modelName) {
|
|
322
|
+
function setSharedEnv(key, value) {
|
|
183
323
|
const config = loadModelConfig();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
.
|
|
235
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
|
428
|
+
.description('Remove model-related configuration from ~/.claude/settings.json')
|
|
291
429
|
.action(() => {
|
|
292
430
|
unsetModelConfig();
|
|
293
431
|
});
|
|
294
432
|
|
|
295
|
-
program
|
|
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": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.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"
|