claude-code-provider-switch 1.1.5 → 1.1.6
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 +88 -13
- package/bin/claude-switch.js +72 -44
- package/lib/anthropic.js +3 -5
- package/lib/config.js +277 -42
- package/lib/menu.js +71 -49
- package/lib/ollama.js +3 -5
- package/lib/openrouter.js +2 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -72,13 +72,16 @@ claude-switch show-defaults
|
|
|
72
72
|
|
|
73
73
|
# Clear all defaults
|
|
74
74
|
claude-switch clear-defaults
|
|
75
|
+
|
|
76
|
+
# Manage API keys interactively
|
|
77
|
+
claude-switch api-keys
|
|
75
78
|
```
|
|
76
79
|
|
|
77
80
|
## ⚙️ Configuration
|
|
78
81
|
|
|
79
82
|
### Environment Variables
|
|
80
83
|
|
|
81
|
-
The CLI is designed to create a
|
|
84
|
+
The CLI is designed to create a `~/.claude/.claude-switch-env` (in your home directory) with this syntax:
|
|
82
85
|
|
|
83
86
|
```env
|
|
84
87
|
# API Keys for different providers
|
|
@@ -92,12 +95,69 @@ ANTHROPIC_MODEL=claude-3-5-sonnet-latest
|
|
|
92
95
|
OLLAMA_MODEL=minimax-m2.5:cloud
|
|
93
96
|
|
|
94
97
|
# Default provider and model settings
|
|
95
|
-
DEFAULT_PROVIDER=original
|
|
98
|
+
DEFAULT_PROVIDER=default # Use 'default' to show menu on startup, or set to 'openrouter', 'anthropic', 'ollama', or 'original'
|
|
96
99
|
DEFAULT_MODEL=
|
|
97
100
|
```
|
|
98
101
|
|
|
99
102
|
When you run the CLI for the first time, based on your selection, it will ask for the API keys or Auth Tokens and after this will be saved in the `.env` file for future use.
|
|
100
103
|
|
|
104
|
+
### Global vs Local Configuration
|
|
105
|
+
|
|
106
|
+
The CLI supports two configuration modes:
|
|
107
|
+
|
|
108
|
+
#### Global Configuration (Default)
|
|
109
|
+
|
|
110
|
+
- **Location**: `~/.claude/.claude-switch-env` (in your home directory)
|
|
111
|
+
- **Priority**: Used when no local `.env` file exists
|
|
112
|
+
- **Benefit**: Share configuration across all projects
|
|
113
|
+
- **Creation**: Automatically created on first run when no local `.env` exists
|
|
114
|
+
|
|
115
|
+
#### Local Configuration
|
|
116
|
+
|
|
117
|
+
- **Location**: `.env` file in your project directory
|
|
118
|
+
- **Priority**: Overrides global configuration when present
|
|
119
|
+
- **Benefit**: Project-specific settings
|
|
120
|
+
- **Creation**: Create manually or use "Save Configuration Locally" menu option
|
|
121
|
+
|
|
122
|
+
#### Configuration Display
|
|
123
|
+
|
|
124
|
+
The main menu shows which configuration source is active:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Configuration: Global (~/.claude/.claude-switch-en)
|
|
128
|
+
Configuration: Local (current_folder/.env)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Menu Behavior
|
|
132
|
+
|
|
133
|
+
- **No defaults set**: Shows interactive menu for provider selection
|
|
134
|
+
- **Defaults set**: Auto-launches with configured provider
|
|
135
|
+
- **Fresh install**: Always shows menu until you set defaults
|
|
136
|
+
|
|
137
|
+
#### Switching Between Modes
|
|
138
|
+
|
|
139
|
+
- **To Local**: Use "Save Configuration Locally" option from main menu
|
|
140
|
+
- **To Global**: Delete local `.env` file to fall back to global config
|
|
141
|
+
- **Priority**: Local always takes precedence over global when both exist
|
|
142
|
+
|
|
143
|
+
### API Key Management
|
|
144
|
+
|
|
145
|
+
For easier API key management, use the interactive menu:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Launch interactive API key management
|
|
149
|
+
claude-switch api-keys
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or access it through the main menu (option 6) when you run `claude-switch` without arguments.
|
|
153
|
+
|
|
154
|
+
**Features:**
|
|
155
|
+
|
|
156
|
+
- 🔐 **Secure**: API keys are masked for display (shows only first 4 and last 4 characters)
|
|
157
|
+
- 🎯 **Visual**: Clear status indicators (✅/❌) show which providers have keys configured
|
|
158
|
+
- ⚡ **Interactive**: Arrow key navigation with visual selection indicators
|
|
159
|
+
- 🔄 **Flexible**: Update, remove, or set new API keys interactively
|
|
160
|
+
|
|
101
161
|
### Provider Setup
|
|
102
162
|
|
|
103
163
|
#### OpenRouter
|
|
@@ -137,13 +197,14 @@ When you run the CLI for the first time, based on your selection, it will ask fo
|
|
|
137
197
|
|
|
138
198
|
### Configuration Commands
|
|
139
199
|
|
|
140
|
-
| Command | Description
|
|
141
|
-
| ---------------- |
|
|
142
|
-
| `set-default` | Interactive default setup
|
|
143
|
-
| `show-defaults` | View current defaults
|
|
144
|
-
| `clear-defaults` | Reset all defaults
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
200
|
+
| Command | Description | Example |
|
|
201
|
+
| ---------------- | ----------------------------- | ------------------------------ |
|
|
202
|
+
| `set-default` | Interactive default setup | `claude-switch set-default` |
|
|
203
|
+
| `show-defaults` | View current defaults | `claude-switch show-defaults` |
|
|
204
|
+
| `clear-defaults` | Reset all defaults | `claude-switch clear-defaults` |
|
|
205
|
+
| `api-keys` | Manage API keys interactively | `claude-switch api-keys` |
|
|
206
|
+
| `help` | Show help information | `claude-switch --help` |
|
|
207
|
+
| `version` | Show Version information | `claude-switch --version` |
|
|
147
208
|
|
|
148
209
|
### Aliases
|
|
149
210
|
|
|
@@ -154,7 +215,8 @@ When you run the CLI for the first time, based on your selection, it will ask fo
|
|
|
154
215
|
| `ollama` | `oll` |
|
|
155
216
|
| `original` | `original`, `orig`, `def`, `d` |
|
|
156
217
|
|
|
157
|
-
|
|
218
|
+
|
|
219
|
+
### ⚠️ Important:Clearing Defaults
|
|
158
220
|
|
|
159
221
|
**If the interactive menu doesn't show and Claude Code opens directly with a predefined provider**, you need to clear the default settings:
|
|
160
222
|
|
|
@@ -164,7 +226,7 @@ claude-switch clear-defaults
|
|
|
164
226
|
|
|
165
227
|
### Why This Happens
|
|
166
228
|
|
|
167
|
-
|
|
229
|
+
When you use the "Set as Default" option, the application saves your provider and model choices to provide a faster experience. However, if you want to change providers or access the full menu again, you must clear these defaults first.
|
|
168
230
|
|
|
169
231
|
### What Clearing Defaults Does
|
|
170
232
|
|
|
@@ -197,12 +259,13 @@ Available providers:
|
|
|
197
259
|
3) Anthropic Aliases: (anthropic, ant)
|
|
198
260
|
4) Original Claude Code Aliases: (original, orig, def, d)
|
|
199
261
|
5) Set as Default Aliases: (set-default)
|
|
200
|
-
6)
|
|
262
|
+
6) Manage API Keys Aliases: (api-keys, keys)
|
|
263
|
+
7) Help Aliases: (help, -h, --help)
|
|
201
264
|
|
|
202
265
|
Controls:
|
|
203
266
|
↑/↓ - Navigate
|
|
204
267
|
Enter - Select provider
|
|
205
|
-
1-
|
|
268
|
+
1-7 - Quick select
|
|
206
269
|
ESC - Exit
|
|
207
270
|
```
|
|
208
271
|
|
|
@@ -218,6 +281,9 @@ const {
|
|
|
218
281
|
getDefaultProvider,
|
|
219
282
|
launchOpenRouter,
|
|
220
283
|
launchAnthropic,
|
|
284
|
+
showApiKeyMenu,
|
|
285
|
+
updateApiKey,
|
|
286
|
+
maskApiKey,
|
|
221
287
|
} = require("claude-code-provider-switch");
|
|
222
288
|
|
|
223
289
|
// Set default provider programmatically
|
|
@@ -229,6 +295,15 @@ console.log(`Current provider: ${current}`);
|
|
|
229
295
|
|
|
230
296
|
// Launch specific provider
|
|
231
297
|
await launchOpenRouter(false, [], "gpt-4");
|
|
298
|
+
|
|
299
|
+
// Manage API keys programmatically
|
|
300
|
+
await showApiKeyMenu(); // Show interactive API key menu
|
|
301
|
+
await updateApiKey({
|
|
302
|
+
id: "openrouter",
|
|
303
|
+
name: "OpenRouter",
|
|
304
|
+
envVar: "OPENROUTER_AUTH_TOKEN",
|
|
305
|
+
}); // Update specific API key
|
|
306
|
+
console.log(maskApiKey("sk-1234567890abcdef")); // "sk-1234...cdef"
|
|
232
307
|
```
|
|
233
308
|
|
|
234
309
|
### Development
|
package/bin/claude-switch.js
CHANGED
|
@@ -187,6 +187,12 @@ async function main(forceMenu = false) {
|
|
|
187
187
|
return;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
if (args[0] === "save-local" || args[0] === "save-locally") {
|
|
191
|
+
const { saveConfigurationLocally } = require("../lib/config");
|
|
192
|
+
await saveConfigurationLocally();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
190
196
|
if (args[0] === "api-keys") {
|
|
191
197
|
const { showApiKeyMenu } = require("../lib/config");
|
|
192
198
|
await showApiKeyMenu();
|
|
@@ -212,7 +218,11 @@ async function main(forceMenu = false) {
|
|
|
212
218
|
const defaultProvider = getDefaultProvider();
|
|
213
219
|
const defaultModel = getDefaultModel();
|
|
214
220
|
|
|
215
|
-
if (
|
|
221
|
+
if (
|
|
222
|
+
defaultProvider &&
|
|
223
|
+
defaultProvider !== null &&
|
|
224
|
+
defaultProvider !== "default"
|
|
225
|
+
) {
|
|
216
226
|
const { log } = require("../lib/config");
|
|
217
227
|
log(
|
|
218
228
|
`Using default: ${defaultProvider}${defaultModel ? ` (${defaultModel})` : ""}`,
|
|
@@ -240,52 +250,70 @@ async function main(forceMenu = false) {
|
|
|
240
250
|
return;
|
|
241
251
|
}
|
|
242
252
|
|
|
243
|
-
// No defaults set, show interactive menu
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
253
|
+
// No defaults set, show interactive menu - use loop to prevent recursion issues
|
|
254
|
+
mainLoop: while (true) {
|
|
255
|
+
const selectedProvider = await showProviderMenu();
|
|
256
|
+
|
|
257
|
+
// Handle special menu options
|
|
258
|
+
switch (selectedProvider.id) {
|
|
259
|
+
case "help":
|
|
260
|
+
showUsage();
|
|
261
|
+
return;
|
|
262
|
+
case "set-default":
|
|
263
|
+
await setupDefaults();
|
|
264
|
+
handlePostConfiguration("restart", true);
|
|
265
|
+
return;
|
|
266
|
+
case "show-defaults":
|
|
267
|
+
showDefaults();
|
|
268
|
+
return;
|
|
269
|
+
case "api-keys":
|
|
270
|
+
const { showApiKeyMenu } = require("../lib/config");
|
|
271
|
+
await showApiKeyMenu();
|
|
272
|
+
// Continue the loop to show menu again
|
|
273
|
+
continue mainLoop;
|
|
274
|
+
case "save-local":
|
|
275
|
+
const { saveConfigurationLocally, log } = require("../lib/config");
|
|
276
|
+
await saveConfigurationLocally();
|
|
277
|
+
// Show menu again after saving locally
|
|
278
|
+
log("Press Enter to continue...", "cyan");
|
|
279
|
+
const continueRl = require("readline").createInterface({
|
|
280
|
+
input: process.stdin,
|
|
281
|
+
output: process.stdout,
|
|
282
|
+
});
|
|
283
|
+
await new Promise((resolve) => {
|
|
284
|
+
continueRl.question("", () => {
|
|
285
|
+
continueRl.close();
|
|
286
|
+
resolve();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
// Continue the loop to show menu again
|
|
290
|
+
continue mainLoop;
|
|
291
|
+
}
|
|
266
292
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
293
|
+
// For provider selection, show model selection
|
|
294
|
+
let selectedModel = null;
|
|
295
|
+
if (selectedProvider.id !== "original") {
|
|
296
|
+
selectedModel = await showModelSelectionForProvider(selectedProvider);
|
|
297
|
+
}
|
|
272
298
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
299
|
+
// Launch the selected provider with the selected model
|
|
300
|
+
switch (selectedProvider.id) {
|
|
301
|
+
case "openrouter":
|
|
302
|
+
await launchOpenRouter(false, [], selectedModel);
|
|
303
|
+
break;
|
|
304
|
+
case "anthropic":
|
|
305
|
+
await launchAnthropic(false, [], selectedModel);
|
|
306
|
+
break;
|
|
307
|
+
case "ollama":
|
|
308
|
+
const modelToUse = selectedModel || getProviderDefaultModel("ollama");
|
|
309
|
+
await launchOllama(false, [], modelToUse);
|
|
310
|
+
break;
|
|
311
|
+
case "original":
|
|
312
|
+
await launchDefault([]);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
287
316
|
}
|
|
288
|
-
return;
|
|
289
317
|
}
|
|
290
318
|
|
|
291
319
|
const command = args[0].toLowerCase();
|
package/lib/anthropic.js
CHANGED
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
log,
|
|
9
9
|
loadEnvFile,
|
|
10
10
|
promptForApiKey,
|
|
11
|
-
|
|
11
|
+
updateConfigFile,
|
|
12
12
|
findBestMatchingModel,
|
|
13
13
|
} = require("./config");
|
|
14
14
|
const { modelCache } = require("./cache");
|
|
@@ -151,7 +151,7 @@ async function showModelSelection() {
|
|
|
151
151
|
log("Anthropic API key is required for model selection", "red");
|
|
152
152
|
process.exit(1);
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
updateConfigFile("ANTHROPIC_API_KEY", newApiKey, null);
|
|
155
155
|
apiKey = newApiKey;
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -436,8 +436,6 @@ async function launchAnthropic(
|
|
|
436
436
|
extraArgs = [],
|
|
437
437
|
directModel = null,
|
|
438
438
|
) {
|
|
439
|
-
log("Launching Claude Code with Anthropic settings...", "green");
|
|
440
|
-
|
|
441
439
|
const envVars = loadEnvFile();
|
|
442
440
|
log(`Loading environment from: ${envVars.envFile}`, "yellow");
|
|
443
441
|
|
|
@@ -448,7 +446,7 @@ async function launchAnthropic(
|
|
|
448
446
|
log("Error: Anthropic API key is required", "red");
|
|
449
447
|
process.exit(1);
|
|
450
448
|
}
|
|
451
|
-
|
|
449
|
+
updateConfigFile("ANTHROPIC_API_KEY", apiKey, null);
|
|
452
450
|
envVars.ANTHROPIC_API_KEY = apiKey;
|
|
453
451
|
}
|
|
454
452
|
|
package/lib/config.js
CHANGED
|
@@ -23,14 +23,236 @@ function log(message, color = "reset") {
|
|
|
23
23
|
console.log(`${colorCode}${message}${colors.reset}`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Get user home directory for global configuration
|
|
27
|
+
const os = require("os");
|
|
28
|
+
const homeDir = os.homedir();
|
|
29
|
+
const claudeDir = path.join(homeDir, ".claude");
|
|
30
|
+
const globalEnvFile = path.join(claudeDir, ".claude-switch-env");
|
|
31
|
+
|
|
26
32
|
// Get script directory (use current working directory for global installs)
|
|
27
33
|
const scriptPath = process.cwd();
|
|
28
|
-
const
|
|
34
|
+
const localEnvFile = path.join(scriptPath, ".env");
|
|
35
|
+
|
|
36
|
+
// Use global env file by default, fallback to local
|
|
37
|
+
const envFile = globalEnvFile;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get configuration source (Local or Global)
|
|
41
|
+
*/
|
|
42
|
+
function getConfigurationSource() {
|
|
43
|
+
if (fs.existsSync(localEnvFile)) {
|
|
44
|
+
return "Local";
|
|
45
|
+
}
|
|
46
|
+
return "Global";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get configuration file path for display
|
|
51
|
+
*/
|
|
52
|
+
function getConfigurationPath() {
|
|
53
|
+
if (fs.existsSync(localEnvFile)) {
|
|
54
|
+
return localEnvFile;
|
|
55
|
+
}
|
|
56
|
+
return globalEnvFile;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if global configuration exists and has meaningful content
|
|
61
|
+
*/
|
|
62
|
+
function hasGlobalConfiguration() {
|
|
63
|
+
try {
|
|
64
|
+
if (!fs.existsSync(globalEnvFile)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const globalConfig = loadSingleEnvFile(globalEnvFile);
|
|
69
|
+
|
|
70
|
+
// Check for at least one API key or default setting
|
|
71
|
+
const hasApiKey =
|
|
72
|
+
globalConfig.OPENROUTER_AUTH_TOKEN ||
|
|
73
|
+
globalConfig.ANTHROPIC_API_KEY ||
|
|
74
|
+
globalConfig.OLLAMA_AUTH_TOKEN;
|
|
75
|
+
|
|
76
|
+
const hasDefaultSetting =
|
|
77
|
+
globalConfig.DEFAULT_PROVIDER || globalConfig.DEFAULT_MODEL;
|
|
78
|
+
|
|
79
|
+
return !!(hasApiKey || hasDefaultSetting);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Save configuration locally by copying global config to local .env
|
|
87
|
+
*/
|
|
88
|
+
async function saveConfigurationLocally() {
|
|
89
|
+
// Ensure log is available in this context
|
|
90
|
+
const localLog = log;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Check if global config exists
|
|
94
|
+
if (!fs.existsSync(globalEnvFile)) {
|
|
95
|
+
localLog("No global configuration found to copy.", "red");
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if local file already exists
|
|
100
|
+
if (fs.existsSync(localEnvFile)) {
|
|
101
|
+
localLog("Local .env file already exists.", "yellow");
|
|
102
|
+
localLog("This will overwrite your local configuration.", "yellow");
|
|
103
|
+
|
|
104
|
+
// Ask for confirmation
|
|
105
|
+
const readline = require("readline");
|
|
106
|
+
const rl = readline.createInterface({
|
|
107
|
+
input: process.stdin,
|
|
108
|
+
output: process.stdout,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const answer = await new Promise((resolve) => {
|
|
112
|
+
rl.question("Do you want to continue? (y/N): ", resolve);
|
|
113
|
+
});
|
|
114
|
+
rl.close();
|
|
115
|
+
|
|
116
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
117
|
+
localLog("Operation cancelled.", "yellow");
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Copy global config to local
|
|
123
|
+
const globalContent = fs.readFileSync(globalEnvFile, "utf8");
|
|
124
|
+
fs.writeFileSync(localEnvFile, globalContent, "utf8");
|
|
125
|
+
|
|
126
|
+
// Clear cache to reflect changes
|
|
127
|
+
envCache = null;
|
|
128
|
+
envCacheTime = null;
|
|
129
|
+
|
|
130
|
+
localLog("Configuration saved locally!", "green");
|
|
131
|
+
localLog(`Local file created: ${localEnvFile}`, "cyan");
|
|
132
|
+
localLog("All future updates will use the local configuration.", "yellow");
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
localLog(`Error saving configuration locally: ${error.message}`, "red");
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Load a single environment file without recursion
|
|
143
|
+
*/
|
|
144
|
+
function loadSingleEnvFile(filePath) {
|
|
145
|
+
try {
|
|
146
|
+
// Handle file existence race condition
|
|
147
|
+
if (!fs.existsSync(filePath)) {
|
|
148
|
+
if (filePath === globalEnvFile) {
|
|
149
|
+
log("Global environment file not found. Creating one...", "yellow");
|
|
150
|
+
createEnvFile(globalEnvFile);
|
|
151
|
+
}
|
|
152
|
+
// Return empty object for missing files (don't try to read)
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Read file with error handling
|
|
157
|
+
const envContent = fs.readFileSync(filePath, "utf8");
|
|
158
|
+
const envVars = {};
|
|
159
|
+
|
|
160
|
+
envContent.split("\n").forEach((line) => {
|
|
161
|
+
const trimmed = line.trim();
|
|
162
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
163
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
164
|
+
if (key && valueParts.length > 0) {
|
|
165
|
+
envVars[key.trim()] = valueParts.join("=").trim();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return envVars;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
log(`Error loading environment file ${filePath}: ${error.message}`, "red");
|
|
173
|
+
// Return empty object on error to prevent crashes
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get configuration from both global and local files
|
|
180
|
+
* Local settings override global settings
|
|
181
|
+
*/
|
|
182
|
+
function loadConfigFiles() {
|
|
183
|
+
const globalConfig = loadSingleEnvFile(globalEnvFile);
|
|
184
|
+
const localConfig = loadSingleEnvFile(localEnvFile);
|
|
185
|
+
|
|
186
|
+
// Merge configs, local takes precedence
|
|
187
|
+
return { ...globalConfig, ...localConfig };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Update configuration with smart targeting (local if exists, global if not)
|
|
192
|
+
*/
|
|
193
|
+
function updateConfigFile(key, value, useGlobal = null) {
|
|
194
|
+
// Smart targeting: if useGlobal is null, determine based on file existence
|
|
195
|
+
let targetFile;
|
|
196
|
+
if (useGlobal === null) {
|
|
197
|
+
targetFile = fs.existsSync(localEnvFile) ? localEnvFile : globalEnvFile;
|
|
198
|
+
} else {
|
|
199
|
+
targetFile = useGlobal ? globalEnvFile : localEnvFile;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
// Ensure the directory exists
|
|
204
|
+
const targetDir = path.dirname(targetFile);
|
|
205
|
+
if (!fs.existsSync(targetDir)) {
|
|
206
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let content = "";
|
|
210
|
+
|
|
211
|
+
// Read existing file or create new one
|
|
212
|
+
if (fs.existsSync(targetFile)) {
|
|
213
|
+
content = fs.readFileSync(targetFile, "utf8");
|
|
214
|
+
} else {
|
|
215
|
+
createEnvFile(targetFile);
|
|
216
|
+
content = fs.readFileSync(targetFile, "utf8");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const lines = content.split("\n");
|
|
220
|
+
let keyFound = false;
|
|
221
|
+
|
|
222
|
+
// Update or add the key
|
|
223
|
+
const updatedLines = lines.map((line) => {
|
|
224
|
+
if (line.startsWith(`${key}=`)) {
|
|
225
|
+
keyFound = true;
|
|
226
|
+
return `${key}=${value}`;
|
|
227
|
+
}
|
|
228
|
+
return line;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!keyFound) {
|
|
232
|
+
updatedLines.push(`${key}=${value}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Write back to file
|
|
236
|
+
fs.writeFileSync(targetFile, updatedLines.join("\n"), "utf8");
|
|
237
|
+
|
|
238
|
+
// Atomically invalidate cache
|
|
239
|
+
envCache = null;
|
|
240
|
+
envCacheTime = null;
|
|
241
|
+
|
|
242
|
+
log(
|
|
243
|
+
`Updated ${useGlobal ? "global" : "local"} configuration: ${key}`,
|
|
244
|
+
"green",
|
|
245
|
+
);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
log(`Error updating configuration file: ${error.message}`, "red");
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
29
251
|
|
|
30
252
|
/**
|
|
31
253
|
* Create default .env file if it doesn't exist
|
|
32
254
|
*/
|
|
33
|
-
function createEnvFile() {
|
|
255
|
+
function createEnvFile(targetPath = globalEnvFile) {
|
|
34
256
|
const defaultEnvContent = `# API Keys for different providers
|
|
35
257
|
OPENROUTER_AUTH_TOKEN=
|
|
36
258
|
ANTHROPIC_API_KEY=
|
|
@@ -42,14 +264,20 @@ ANTHROPIC_MODEL=claude-3-5-sonnet-latest
|
|
|
42
264
|
OLLAMA_MODEL=minimax-m2.5:cloud
|
|
43
265
|
|
|
44
266
|
# Default provider and model settings
|
|
45
|
-
DEFAULT_PROVIDER=
|
|
267
|
+
DEFAULT_PROVIDER=default
|
|
46
268
|
DEFAULT_MODEL=
|
|
47
269
|
`;
|
|
48
270
|
|
|
49
271
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
272
|
+
// Ensure the directory exists
|
|
273
|
+
const targetDir = path.dirname(targetPath);
|
|
274
|
+
if (!fs.existsSync(targetDir)) {
|
|
275
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
276
|
+
log(`Created directory: ${targetDir}`, "green");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fs.writeFileSync(targetPath, defaultEnvContent, "utf8");
|
|
280
|
+
log(`Created environment file: ${targetPath}`, "green");
|
|
53
281
|
return true;
|
|
54
282
|
} catch (error) {
|
|
55
283
|
log(`Error creating .env file: ${error.message}`, "red");
|
|
@@ -127,6 +355,7 @@ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
|
127
355
|
|
|
128
356
|
/**
|
|
129
357
|
* Load environment variables from .env file with caching
|
|
358
|
+
* Uses merged configuration from global and local files
|
|
130
359
|
*/
|
|
131
360
|
function loadEnvFile() {
|
|
132
361
|
const now = Date.now();
|
|
@@ -137,38 +366,25 @@ function loadEnvFile() {
|
|
|
137
366
|
}
|
|
138
367
|
|
|
139
368
|
try {
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
log("Environment file not found. Creating one...", "yellow");
|
|
143
|
-
createEnvFile();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Read file with error handling
|
|
147
|
-
const envContent = fs.readFileSync(envFile, "utf8");
|
|
148
|
-
const envVars = {};
|
|
149
|
-
|
|
150
|
-
envContent.split("\n").forEach((line) => {
|
|
151
|
-
const trimmed = line.trim();
|
|
152
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
153
|
-
const [key, ...valueParts] = trimmed.split("=");
|
|
154
|
-
if (key && valueParts.length > 0) {
|
|
155
|
-
envVars[key.trim()] = valueParts.join("=").trim();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
});
|
|
369
|
+
// Load and merge global and local configurations
|
|
370
|
+
const mergedConfig = loadConfigFiles();
|
|
159
371
|
|
|
160
372
|
// Atomically update cache
|
|
161
|
-
envCache =
|
|
373
|
+
envCache = mergedConfig;
|
|
162
374
|
envCacheTime = now;
|
|
163
375
|
|
|
164
376
|
// Include envFile path in the returned object for debugging
|
|
165
|
-
|
|
377
|
+
// Point envFile to the actual active configuration source
|
|
378
|
+
mergedConfig.envFile = fs.existsSync(localEnvFile)
|
|
379
|
+
? localEnvFile
|
|
380
|
+
: globalEnvFile;
|
|
381
|
+
mergedConfig.localEnvFile = localEnvFile;
|
|
166
382
|
|
|
167
|
-
return
|
|
383
|
+
return mergedConfig;
|
|
168
384
|
} catch (error) {
|
|
169
|
-
log(`Error loading environment
|
|
385
|
+
log(`Error loading environment files: ${error.message}`, "red");
|
|
170
386
|
// Return empty object on error to prevent crashes
|
|
171
|
-
return { envFile };
|
|
387
|
+
return { envFile: globalEnvFile, localEnvFile };
|
|
172
388
|
}
|
|
173
389
|
}
|
|
174
390
|
|
|
@@ -210,7 +426,7 @@ function findBestMatchingModel(searchTerm, models) {
|
|
|
210
426
|
*/
|
|
211
427
|
function getDefaultProvider(envVars = null) {
|
|
212
428
|
const vars = envVars || loadEnvFile();
|
|
213
|
-
return vars.DEFAULT_PROVIDER ||
|
|
429
|
+
return vars.DEFAULT_PROVIDER || null;
|
|
214
430
|
}
|
|
215
431
|
|
|
216
432
|
/**
|
|
@@ -222,7 +438,7 @@ function getDefaultModel(envVars = null) {
|
|
|
222
438
|
}
|
|
223
439
|
|
|
224
440
|
/**
|
|
225
|
-
* Set default provider
|
|
441
|
+
* Set default provider with smart targeting (local if exists, global if not)
|
|
226
442
|
*/
|
|
227
443
|
function setDefaultProvider(provider) {
|
|
228
444
|
// Validate input
|
|
@@ -230,12 +446,12 @@ function setDefaultProvider(provider) {
|
|
|
230
446
|
throw new Error("Provider must be a non-empty string");
|
|
231
447
|
}
|
|
232
448
|
|
|
233
|
-
|
|
449
|
+
updateConfigFile("DEFAULT_PROVIDER", provider.trim(), null);
|
|
234
450
|
log(`Default provider set to: ${provider.trim()}`, "green");
|
|
235
451
|
}
|
|
236
452
|
|
|
237
453
|
/**
|
|
238
|
-
* Set default model
|
|
454
|
+
* Set default model with smart targeting (local if exists, global if not)
|
|
239
455
|
*/
|
|
240
456
|
function setDefaultModel(model) {
|
|
241
457
|
// Validate input (allow empty string for clearing)
|
|
@@ -243,7 +459,7 @@ function setDefaultModel(model) {
|
|
|
243
459
|
throw new Error("Model must be a string");
|
|
244
460
|
}
|
|
245
461
|
|
|
246
|
-
|
|
462
|
+
updateConfigFile("DEFAULT_MODEL", model.trim(), null);
|
|
247
463
|
log(`Default model set to: ${model.trim()}`, "green");
|
|
248
464
|
}
|
|
249
465
|
|
|
@@ -328,9 +544,9 @@ async function showApiKeyMenu() {
|
|
|
328
544
|
process.stdin.removeAllListeners("keypress");
|
|
329
545
|
rl.close();
|
|
330
546
|
updateApiKey(providers[selectedIndex]).then(resolve);
|
|
331
|
-
} else if (
|
|
332
|
-
const index = parseInt(
|
|
333
|
-
if (index < providers.length) {
|
|
547
|
+
} else if (str && /^[1-3]$/.test(str)) {
|
|
548
|
+
const index = parseInt(str) - 1;
|
|
549
|
+
if (index >= 0 && index < providers.length) {
|
|
334
550
|
selectedIndex = index;
|
|
335
551
|
process.stdin.removeAllListeners("keypress");
|
|
336
552
|
rl.close();
|
|
@@ -339,7 +555,16 @@ async function showApiKeyMenu() {
|
|
|
339
555
|
}
|
|
340
556
|
};
|
|
341
557
|
|
|
558
|
+
// Set up raw mode for arrow key detection
|
|
559
|
+
if (process.stdin.setRawMode) {
|
|
560
|
+
process.stdin.setRawMode(true);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Enable keypress events
|
|
564
|
+
readline.emitKeypressEvents(process.stdin);
|
|
565
|
+
|
|
342
566
|
process.stdin.on("keypress", handleKeyPress);
|
|
567
|
+
rl.question("", () => {});
|
|
343
568
|
});
|
|
344
569
|
}
|
|
345
570
|
|
|
@@ -428,11 +653,11 @@ async function updateApiKey(provider) {
|
|
|
428
653
|
if (choice === "update") {
|
|
429
654
|
const newKey = await promptForApiKey(provider.name, provider.envVar);
|
|
430
655
|
if (newKey) {
|
|
431
|
-
|
|
656
|
+
updateConfigFile(provider.envVar, newKey, null);
|
|
432
657
|
log(`${provider.name} API key updated successfully!`, "green");
|
|
433
658
|
}
|
|
434
659
|
} else if (choice === "remove") {
|
|
435
|
-
|
|
660
|
+
updateConfigFile(provider.envVar, "", null);
|
|
436
661
|
log(`${provider.name} API key removed!`, "yellow");
|
|
437
662
|
} else {
|
|
438
663
|
log("Operation cancelled.", "yellow");
|
|
@@ -442,7 +667,7 @@ async function updateApiKey(provider) {
|
|
|
442
667
|
log("", "reset");
|
|
443
668
|
const newKey = await promptForApiKey(provider.name, provider.envVar);
|
|
444
669
|
if (newKey) {
|
|
445
|
-
|
|
670
|
+
updateConfigFile(provider.envVar, newKey, null);
|
|
446
671
|
log(`${provider.name} API key set successfully!`, "green");
|
|
447
672
|
} else {
|
|
448
673
|
log("Operation cancelled.", "yellow");
|
|
@@ -481,11 +706,17 @@ function maskApiKey(apiKey) {
|
|
|
481
706
|
module.exports = {
|
|
482
707
|
colors,
|
|
483
708
|
log,
|
|
484
|
-
envFile,
|
|
709
|
+
envFile: globalEnvFile,
|
|
710
|
+
localEnvFile,
|
|
711
|
+
globalEnvFile,
|
|
712
|
+
claudeDir,
|
|
485
713
|
createEnvFile,
|
|
486
714
|
promptForApiKey,
|
|
487
715
|
updateEnvFile,
|
|
716
|
+
updateConfigFile,
|
|
488
717
|
loadEnvFile,
|
|
718
|
+
loadSingleEnvFile,
|
|
719
|
+
loadConfigFiles,
|
|
489
720
|
findBestMatchingModel,
|
|
490
721
|
getDefaultProvider,
|
|
491
722
|
getDefaultModel,
|
|
@@ -495,4 +726,8 @@ module.exports = {
|
|
|
495
726
|
showApiKeyMenu,
|
|
496
727
|
updateApiKey,
|
|
497
728
|
maskApiKey,
|
|
729
|
+
getConfigurationSource,
|
|
730
|
+
getConfigurationPath,
|
|
731
|
+
hasGlobalConfiguration,
|
|
732
|
+
saveConfigurationLocally,
|
|
498
733
|
};
|
package/lib/menu.js
CHANGED
|
@@ -10,6 +10,9 @@ const {
|
|
|
10
10
|
setDefaultProvider,
|
|
11
11
|
setDefaultModel,
|
|
12
12
|
showApiKeyMenu,
|
|
13
|
+
getConfigurationSource,
|
|
14
|
+
getConfigurationPath,
|
|
15
|
+
hasGlobalConfiguration,
|
|
13
16
|
} = require("./config");
|
|
14
17
|
const {
|
|
15
18
|
showModelSelection: showOpenRouterModelSelection,
|
|
@@ -28,12 +31,6 @@ async function showModelSelectionForProvider(provider) {
|
|
|
28
31
|
|
|
29
32
|
switch (provider.id) {
|
|
30
33
|
case "openrouter":
|
|
31
|
-
if (!envVars.OPENROUTER_AUTH_TOKEN) {
|
|
32
|
-
log("OpenRouter auth token required for model selection", "red");
|
|
33
|
-
log("Please set OPENROUTER_AUTH_TOKEN in .env file", "yellow");
|
|
34
|
-
log("Using default model...", "yellow");
|
|
35
|
-
return getProviderDefaultModel("openrouter");
|
|
36
|
-
}
|
|
37
34
|
return await showOpenRouterModelSelection();
|
|
38
35
|
|
|
39
36
|
case "ollama":
|
|
@@ -60,10 +57,12 @@ function showProviderMenu() {
|
|
|
60
57
|
output: process.stdout,
|
|
61
58
|
});
|
|
62
59
|
|
|
63
|
-
// Get current defaults
|
|
60
|
+
// Get current defaults and configuration source
|
|
64
61
|
const defaultProvider = getDefaultProvider();
|
|
65
62
|
const defaultModel = getDefaultModel();
|
|
63
|
+
const configSource = getConfigurationSource();
|
|
66
64
|
|
|
65
|
+
// Build providers array dynamically
|
|
67
66
|
const providers = [
|
|
68
67
|
{
|
|
69
68
|
id: "openrouter",
|
|
@@ -79,9 +78,24 @@ function showProviderMenu() {
|
|
|
79
78
|
},
|
|
80
79
|
{ id: "set-default", name: "Set as Default", aliases: ["set-default"] },
|
|
81
80
|
{ id: "api-keys", name: "Manage API Keys", aliases: ["api-keys", "keys"] },
|
|
82
|
-
{ id: "help", name: "Help", aliases: ["help", "-h", "--help"] },
|
|
83
81
|
];
|
|
84
82
|
|
|
83
|
+
// Add "Save Configuration Locally" option only if global configuration exists
|
|
84
|
+
if (hasGlobalConfiguration()) {
|
|
85
|
+
providers.push({
|
|
86
|
+
id: "save-local",
|
|
87
|
+
name: "Save Configuration Locally",
|
|
88
|
+
aliases: ["save-local", "local", "save-locally"],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Always add Help at the end
|
|
93
|
+
providers.push({
|
|
94
|
+
id: "help",
|
|
95
|
+
name: "Help",
|
|
96
|
+
aliases: ["help", "-h", "--help"],
|
|
97
|
+
});
|
|
98
|
+
|
|
85
99
|
let selectedIndex = 0;
|
|
86
100
|
|
|
87
101
|
function displayMenu() {
|
|
@@ -89,8 +103,18 @@ function showProviderMenu() {
|
|
|
89
103
|
log("Claude Code Provider Switcher", "green");
|
|
90
104
|
log("", "reset");
|
|
91
105
|
|
|
106
|
+
// Show configuration source with file path
|
|
107
|
+
const configSource = getConfigurationSource();
|
|
108
|
+
const configPath = getConfigurationPath();
|
|
109
|
+
log(`Configuration: ${configSource} (${configPath})`, "cyan");
|
|
110
|
+
log("", "reset");
|
|
111
|
+
|
|
92
112
|
// Show current defaults
|
|
93
|
-
if (
|
|
113
|
+
if (
|
|
114
|
+
defaultProvider &&
|
|
115
|
+
defaultProvider !== null &&
|
|
116
|
+
defaultProvider !== "default"
|
|
117
|
+
) {
|
|
94
118
|
const providerName =
|
|
95
119
|
providers.find((p) => p.id === defaultProvider)?.name ||
|
|
96
120
|
defaultProvider;
|
|
@@ -100,10 +124,8 @@ function showProviderMenu() {
|
|
|
100
124
|
`Current default: ${providerName}${currentModel ? ` (${currentModel})` : ""}`,
|
|
101
125
|
"yellow",
|
|
102
126
|
);
|
|
103
|
-
|
|
104
|
-
log("No default provider set", "yellow");
|
|
127
|
+
log("", "reset");
|
|
105
128
|
}
|
|
106
|
-
log("", "reset");
|
|
107
129
|
|
|
108
130
|
log("Available providers:", "yellow");
|
|
109
131
|
log("", "reset");
|
|
@@ -135,51 +157,47 @@ function showProviderMenu() {
|
|
|
135
157
|
);
|
|
136
158
|
});
|
|
137
159
|
|
|
138
|
-
log("", "reset");
|
|
139
|
-
log("Controls:", "yellow");
|
|
140
|
-
log("↑/↓ - Navigate", "reset");
|
|
141
|
-
log("Enter - Select provider", "reset");
|
|
142
|
-
log("1-7 - Quick select", "reset");
|
|
143
|
-
log("ESC - Exit", "reset");
|
|
144
160
|
log("", "reset");
|
|
145
161
|
|
|
146
162
|
// Add helpful usage text
|
|
147
163
|
log("💡 Quick Start:", "cyan");
|
|
148
164
|
log("• Use ↑/↓ arrows to navigate providers", "reset");
|
|
149
165
|
log("• Press Enter to launch selected provider", "reset");
|
|
166
|
+
log(`• Press 1-${providers.length} for quick select`, "reset");
|
|
150
167
|
log("• Use 'Set as Default' to save your preferred provider", "reset");
|
|
151
168
|
log("", "reset");
|
|
152
|
-
|
|
153
|
-
log(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
log(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
log("
|
|
172
|
-
log(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
log("
|
|
182
|
-
log("", "
|
|
169
|
+
// Commented out Commands section for cleaner menu
|
|
170
|
+
// log("Commands:", "yellow");
|
|
171
|
+
// log(
|
|
172
|
+
// " claude-switch - Show menu or use default",
|
|
173
|
+
// "reset",
|
|
174
|
+
// );
|
|
175
|
+
// log(
|
|
176
|
+
// " claude-switch openrouter - Use OpenRouter provider",
|
|
177
|
+
// "reset",
|
|
178
|
+
// );
|
|
179
|
+
// log(
|
|
180
|
+
// " claude-switch anthropic - Use Anthropic provider",
|
|
181
|
+
// "reset",
|
|
182
|
+
// );
|
|
183
|
+
// log(" claude-switch ollama - Use Ollama provider", "reset");
|
|
184
|
+
// log(
|
|
185
|
+
// " claude-switch set-default - Setup default provider",
|
|
186
|
+
// "reset",
|
|
187
|
+
// );
|
|
188
|
+
// log("", "reset");
|
|
189
|
+
// log("Model Selection:", "yellow");
|
|
190
|
+
// log(
|
|
191
|
+
// " claude-switch openrouter --model - Select OpenRouter model",
|
|
192
|
+
// "reset",
|
|
193
|
+
// );
|
|
194
|
+
// log(
|
|
195
|
+
// " claude-switch anthropic --model - Select Anthropic model",
|
|
196
|
+
// "reset",
|
|
197
|
+
// );
|
|
198
|
+
// log("", "reset");
|
|
199
|
+
// log("Type 'claude-switch --help' for complete documentation", "cyan");
|
|
200
|
+
// log("", "reset");
|
|
183
201
|
}
|
|
184
202
|
|
|
185
203
|
displayMenu();
|
|
@@ -489,6 +507,10 @@ function showUsage() {
|
|
|
489
507
|
log(" show-defaults - Display current default settings", "reset");
|
|
490
508
|
log(" clear-defaults - Reset all default settings", "reset");
|
|
491
509
|
log(" api-keys - Manage API keys for providers", "reset");
|
|
510
|
+
log(
|
|
511
|
+
" save-local - Save global configuration to local .env file",
|
|
512
|
+
"reset",
|
|
513
|
+
);
|
|
492
514
|
log("", "reset");
|
|
493
515
|
log("Options:", "reset");
|
|
494
516
|
log(" --model - Show interactive model selection menu", "reset");
|
package/lib/ollama.js
CHANGED
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
loadEnvFile,
|
|
11
11
|
findBestMatchingModel,
|
|
12
12
|
promptForApiKey,
|
|
13
|
-
|
|
13
|
+
updateConfigFile,
|
|
14
14
|
} = require("./config");
|
|
15
15
|
const { modelCache } = require("./cache");
|
|
16
16
|
const { OLLAMA, CACHE, HTTP_STATUS, DEFAULT_MODELS } = require("./constants");
|
|
@@ -97,7 +97,7 @@ async function showModelSelection() {
|
|
|
97
97
|
log("Ollama auth token is required for model selection", "red");
|
|
98
98
|
process.exit(1);
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
updateConfigFile("OLLAMA_AUTH_TOKEN", newToken, null);
|
|
101
101
|
authToken = newToken;
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -378,8 +378,6 @@ async function launchOllama(
|
|
|
378
378
|
extraArgs = [],
|
|
379
379
|
directModel = null,
|
|
380
380
|
) {
|
|
381
|
-
log("Launching Claude Code with Ollama settings...", "green");
|
|
382
|
-
|
|
383
381
|
const envVars = loadEnvFile();
|
|
384
382
|
log(`Loading environment from: ${envVars.envFile}`, "yellow");
|
|
385
383
|
|
|
@@ -391,7 +389,7 @@ async function launchOllama(
|
|
|
391
389
|
log("Press Enter to skip, or provide an auth token:", "reset");
|
|
392
390
|
authToken = await promptForApiKey("Ollama (optional)", "OLLAMA_AUTH_TOKEN");
|
|
393
391
|
if (authToken) {
|
|
394
|
-
|
|
392
|
+
updateConfigFile("OLLAMA_AUTH_TOKEN", authToken, null);
|
|
395
393
|
envVars.OLLAMA_AUTH_TOKEN = authToken;
|
|
396
394
|
}
|
|
397
395
|
}
|
package/lib/openrouter.js
CHANGED
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
loadEnvFile,
|
|
10
10
|
findBestMatchingModel,
|
|
11
11
|
promptForApiKey,
|
|
12
|
-
|
|
12
|
+
updateConfigFile,
|
|
13
13
|
} = require("./config");
|
|
14
14
|
const { modelCache } = require("./cache");
|
|
15
15
|
const {
|
|
@@ -435,8 +435,6 @@ async function launchOpenRouter(
|
|
|
435
435
|
extraArgs = [],
|
|
436
436
|
directModel = null,
|
|
437
437
|
) {
|
|
438
|
-
log("Launching Claude Code with OpenRouter settings...", "green");
|
|
439
|
-
|
|
440
438
|
const envVars = loadEnvFile();
|
|
441
439
|
log(`Loading environment from: ${envVars.envFile}`, "yellow");
|
|
442
440
|
|
|
@@ -450,7 +448,7 @@ async function launchOpenRouter(
|
|
|
450
448
|
log("Error: OpenRouter auth token is required", "red");
|
|
451
449
|
process.exit(1);
|
|
452
450
|
}
|
|
453
|
-
|
|
451
|
+
updateConfigFile("OPENROUTER_AUTH_TOKEN", authToken, null);
|
|
454
452
|
envVars.OPENROUTER_AUTH_TOKEN = authToken;
|
|
455
453
|
}
|
|
456
454
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-provider-switch",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Cross-platform Claude Code provider switcher (OpenRouter, Ollama, Anthropic)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/claude-switch.js",
|
|
11
11
|
"dev": "node bin/claude-switch.js",
|
|
12
|
-
"version": "npm version",
|
|
13
12
|
"link": "npm link",
|
|
14
13
|
"unlink": "npm unlink -g claude-code-provider-switch",
|
|
15
14
|
"pub": "npm version patch && npm publish && git push origin main --tags",
|
|
@@ -22,15 +21,16 @@
|
|
|
22
21
|
"test:all": "node test/run-tests.js"
|
|
23
22
|
},
|
|
24
23
|
"keywords": [
|
|
25
|
-
"claude",
|
|
26
24
|
"claude-code",
|
|
27
25
|
"claude code provider switcher",
|
|
26
|
+
"claude code model switcher",
|
|
28
27
|
"claude code llm switcher",
|
|
29
28
|
"claude code llm provider switcher",
|
|
30
29
|
"ai provider switcher",
|
|
31
30
|
"claude code ai model switcher",
|
|
32
31
|
"claude code ai model provider switcher",
|
|
33
|
-
"claude code OpenRouter
|
|
32
|
+
"claude code OpenRouter",
|
|
33
|
+
"claude code Ollama",
|
|
34
34
|
"cli"
|
|
35
35
|
],
|
|
36
36
|
"author": "Adrian R",
|