@sekora_ai/claude-toggle 1.0.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/LICENSE +21 -0
- package/PROVIDERS.md +169 -0
- package/README.md +382 -0
- package/bin/postinstall.js +76 -0
- package/bin/wrapper.js +57 -0
- package/claude-toggle +490 -0
- package/package.json +47 -0
- package/providers.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sean Sekora
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PROVIDERS.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Provider Configuration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to add and configure new AI providers for the `claude-toggle` script.
|
|
4
|
+
|
|
5
|
+
## Configuration File Location
|
|
6
|
+
|
|
7
|
+
The script searches for `providers.json` in the following order:
|
|
8
|
+
|
|
9
|
+
1. `~/.config/claude-providers/providers.json` (recommended)
|
|
10
|
+
2. `~/.claude-providers.json`
|
|
11
|
+
3. `<script-directory>/providers.json`
|
|
12
|
+
|
|
13
|
+
## JSON Schema
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
18
|
+
"version": "1.0",
|
|
19
|
+
"default_provider": "anthropic",
|
|
20
|
+
"providers": {
|
|
21
|
+
"<provider_name>": {
|
|
22
|
+
"description": "Human-readable description",
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"env_vars": {
|
|
25
|
+
"ENV_VAR_NAME": {
|
|
26
|
+
"value": "static-value-or-${OTHER_VAR}",
|
|
27
|
+
"required": false
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"validation": {
|
|
31
|
+
"required_env_vars": ["API_KEY_VAR"],
|
|
32
|
+
"optional_commands": ["command --version"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration Fields
|
|
40
|
+
|
|
41
|
+
### Top Level
|
|
42
|
+
|
|
43
|
+
| Field | Type | Required | Description |
|
|
44
|
+
|-------|------|----------|-------------|
|
|
45
|
+
| `version` | string | Yes | Configuration format version (use "1.0") |
|
|
46
|
+
| `default_provider` | string | No | Default provider name (defaults to "anthropic") |
|
|
47
|
+
| `providers` | object | Yes | Provider configurations keyed by name |
|
|
48
|
+
|
|
49
|
+
### Provider Object
|
|
50
|
+
|
|
51
|
+
| Field | Type | Required | Description |
|
|
52
|
+
|-------|------|----------|-------------|
|
|
53
|
+
| `description` | string | No | Human-readable description of the provider |
|
|
54
|
+
| `enabled` | boolean | Yes | Whether this provider is available for use |
|
|
55
|
+
| `env_vars` | object | No | Environment variables to set for this provider |
|
|
56
|
+
| `validation` | object | No | Validation rules for this provider |
|
|
57
|
+
|
|
58
|
+
### Environment Variables (`env_vars`)
|
|
59
|
+
|
|
60
|
+
Each key is an environment variable name. Each value is an object with:
|
|
61
|
+
|
|
62
|
+
| Field | Type | Required | Description |
|
|
63
|
+
|-------|------|----------|-------------|
|
|
64
|
+
| `value` | string | Yes | Value to set. Use `${VAR_NAME}` to reference another env var |
|
|
65
|
+
| `required` | boolean | No | Whether this field must have a non-empty value |
|
|
66
|
+
|
|
67
|
+
### Validation (`validation`)
|
|
68
|
+
|
|
69
|
+
| Field | Type | Required | Description |
|
|
70
|
+
|-------|------|----------|-------------|
|
|
71
|
+
| `required_env_vars` | array | No | List of env var names that must be set |
|
|
72
|
+
| `optional_commands` | array | No | Commands that should be available (informational) |
|
|
73
|
+
|
|
74
|
+
## Example: Adding an OpenAI Provider
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"version": "1.0",
|
|
79
|
+
"default_provider": "anthropic",
|
|
80
|
+
"providers": {
|
|
81
|
+
"anthropic": {
|
|
82
|
+
"description": "Anthropic's official API",
|
|
83
|
+
"enabled": true,
|
|
84
|
+
"env_vars": {},
|
|
85
|
+
"validation": {}
|
|
86
|
+
},
|
|
87
|
+
"openai": {
|
|
88
|
+
"description": "OpenAI API (via compatibility layer)",
|
|
89
|
+
"enabled": true,
|
|
90
|
+
"env_vars": {
|
|
91
|
+
"OPENAI_API_KEY": {
|
|
92
|
+
"value": "${OPENAI_API_KEY}",
|
|
93
|
+
"required": true
|
|
94
|
+
},
|
|
95
|
+
"ANTHROPIC_BASE_URL": {
|
|
96
|
+
"value": "https://api.openai.com/v1"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"validation": {
|
|
100
|
+
"required_env_vars": ["OPENAI_API_KEY"]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then use it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
./claude-toggle --provider openai
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Example: Adding a Self-Hosted Provider (Ollama)
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"version": "1.0",
|
|
118
|
+
"providers": {
|
|
119
|
+
"ollama": {
|
|
120
|
+
"description": "Local Ollama instance",
|
|
121
|
+
"enabled": true,
|
|
122
|
+
"env_vars": {
|
|
123
|
+
"ANTHROPIC_BASE_URL": {
|
|
124
|
+
"value": "http://localhost:11434/v1"
|
|
125
|
+
},
|
|
126
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": {
|
|
127
|
+
"value": "llama3.2"
|
|
128
|
+
},
|
|
129
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": {
|
|
130
|
+
"value": "llama3.2"
|
|
131
|
+
},
|
|
132
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": {
|
|
133
|
+
"value": "llama3.2"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"validation": {
|
|
137
|
+
"optional_commands": ["ollama --version"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Tips
|
|
145
|
+
|
|
146
|
+
1. **Variable References**: Use `${VAR_NAME}` in `value` fields to reference environment variables. The script will expand these at runtime.
|
|
147
|
+
|
|
148
|
+
2. **Model Mappings**: Claude Code uses three model tiers (haiku, sonnet, opus). Map these to appropriate models for your provider.
|
|
149
|
+
|
|
150
|
+
3. **API Timeouts**: For slower providers, increase `API_TIMEOUT_MS` (value is in milliseconds).
|
|
151
|
+
|
|
152
|
+
4. **Disable Providers**: Set `"enabled": false` to temporarily disable a provider without removing its configuration.
|
|
153
|
+
|
|
154
|
+
5. **Validation**: Use `required_env_vars` to ensure credentials are set before launching. The script will show helpful setup instructions if they're missing.
|
|
155
|
+
|
|
156
|
+
## Testing Your Configuration
|
|
157
|
+
|
|
158
|
+
After adding a new provider, validate it:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# List all providers
|
|
162
|
+
./claude-toggle --list-providers
|
|
163
|
+
|
|
164
|
+
# Validate specific provider
|
|
165
|
+
./claude-toggle --validate --provider <name>
|
|
166
|
+
|
|
167
|
+
# Test with verbose output
|
|
168
|
+
./claude-toggle --verbose --provider <name> --help
|
|
169
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# claude-toggle
|
|
2
|
+
|
|
3
|
+
A provider-agnostic utility for toggling between different Claude API providers in Claude Code. Switch seamlessly between Anthropic's official API, Z.AI GLM models, OpenAI-compatible endpoints, or self-hosted solutions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Provider-agnostic architecture** - JSON-based configuration for easy provider management
|
|
8
|
+
- **Zero file modifications** - All changes are scoped to the subprocess environment
|
|
9
|
+
- **Pass-through design** - All Claude Code CLI options work transparently
|
|
10
|
+
- **Actionable error messages** - Missing credentials trigger detailed setup instructions
|
|
11
|
+
- **Extensible** - Add new providers by editing a single JSON file
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- Bash (version 4.0+)
|
|
16
|
+
- Python 3 (for JSON parsing, no external packages required)
|
|
17
|
+
- [Claude Code CLI](https://claude.ai/code) installed and available as `claude`
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### Option 1: npm (Recommended)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @sekora_ai/claude-toggle
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This works on macOS, Linux, and Windows (via WSL or Git Bash).
|
|
28
|
+
|
|
29
|
+
### Option 2: Manual Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Clone the repository
|
|
33
|
+
git clone https://github.com/seansekora/claude-toggle.git
|
|
34
|
+
cd claude-toggle
|
|
35
|
+
chmod +x claude-toggle
|
|
36
|
+
|
|
37
|
+
# Copy default config to user directory
|
|
38
|
+
mkdir -p ~/.config/claude-providers
|
|
39
|
+
cp providers.json ~/.config/claude-providers/
|
|
40
|
+
|
|
41
|
+
# Add to PATH (choose your shell)
|
|
42
|
+
# For bash:
|
|
43
|
+
echo 'export PATH="$HOME/path/to/claude-toggle:$PATH"' >> ~/.bashrc
|
|
44
|
+
source ~/.bashrc
|
|
45
|
+
|
|
46
|
+
# For zsh:
|
|
47
|
+
echo 'export PATH="$HOME/path/to/claude-toggle:$PATH"' >> ~/.zshrc
|
|
48
|
+
source ~/.zshrc
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Basic Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Launch with default provider (Anthropic)
|
|
57
|
+
./claude-toggle
|
|
58
|
+
|
|
59
|
+
# Launch with specific provider
|
|
60
|
+
./claude-toggle --provider glm
|
|
61
|
+
|
|
62
|
+
# List available providers
|
|
63
|
+
./claude-toggle --list-providers
|
|
64
|
+
|
|
65
|
+
# Validate provider configuration
|
|
66
|
+
./claude-toggle --validate --provider glm
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Pass-through Options
|
|
70
|
+
|
|
71
|
+
All unrecognized options are forwarded to the Claude Code CLI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Pass options to Claude Code
|
|
75
|
+
./claude-toggle --provider glm --dangerously-skip-permissions
|
|
76
|
+
|
|
77
|
+
# Any Claude Code option works
|
|
78
|
+
./claude-toggle --provider anthropic --help
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Command Reference
|
|
82
|
+
|
|
83
|
+
| Option | Description |
|
|
84
|
+
|--------|-------------|
|
|
85
|
+
| `-p, --provider <name>` | Select a specific provider |
|
|
86
|
+
| `-l, --list-providers` | List all available providers |
|
|
87
|
+
| `--validate` | Validate provider configuration without launching |
|
|
88
|
+
| `-v, --verbose` | Enable verbose output |
|
|
89
|
+
| `-h, --help` | Show help message |
|
|
90
|
+
| `--glm` | Legacy alias for `--provider glm` |
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
### Configuration File Location
|
|
95
|
+
|
|
96
|
+
The script searches for `providers.json` in this order:
|
|
97
|
+
|
|
98
|
+
1. `~/.config/claude-providers/providers.json` (recommended)
|
|
99
|
+
2. `~/.claude-providers.json`
|
|
100
|
+
3. `<script-directory>/providers.json`
|
|
101
|
+
|
|
102
|
+
### Provider Configuration
|
|
103
|
+
|
|
104
|
+
Providers are configured in `providers.json`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"version": "1.0",
|
|
109
|
+
"default_provider": "anthropic",
|
|
110
|
+
"providers": {
|
|
111
|
+
"anthropic": {
|
|
112
|
+
"description": "Anthropic's official API",
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"env_vars": {},
|
|
115
|
+
"validation": {}
|
|
116
|
+
},
|
|
117
|
+
"glm": {
|
|
118
|
+
"description": "Z.AI GLM models via proxy",
|
|
119
|
+
"enabled": true,
|
|
120
|
+
"env_vars": {
|
|
121
|
+
"ANTHROPIC_AUTH_TOKEN": {
|
|
122
|
+
"value": "${ZAI_API_KEY}",
|
|
123
|
+
"required": true
|
|
124
|
+
},
|
|
125
|
+
"ANTHROPIC_BASE_URL": {
|
|
126
|
+
"value": "https://api.z.ai/api/anthropic"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"validation": {
|
|
130
|
+
"required_env_vars": ["ZAI_API_KEY"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Adding New Providers
|
|
138
|
+
|
|
139
|
+
**Interactive wizard (recommended):**
|
|
140
|
+
|
|
141
|
+
If you're using Claude Code in this repository, run:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
/add-provider
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This walks you through the entire process interactively.
|
|
148
|
+
|
|
149
|
+
**Manual configuration:**
|
|
150
|
+
|
|
151
|
+
See [PROVIDERS.md](PROVIDERS.md) for detailed instructions on adding new providers.
|
|
152
|
+
|
|
153
|
+
**Quick example:**
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
"my-provider": {
|
|
157
|
+
"description": "My custom provider",
|
|
158
|
+
"enabled": true,
|
|
159
|
+
"env_vars": {
|
|
160
|
+
"ANTHROPIC_AUTH_TOKEN": {"value": "${MY_API_KEY}"},
|
|
161
|
+
"ANTHROPIC_BASE_URL": {"value": "https://api.example.com/v1"}
|
|
162
|
+
},
|
|
163
|
+
"validation": {
|
|
164
|
+
"required_env_vars": ["MY_API_KEY"]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Built-in Providers
|
|
170
|
+
|
|
171
|
+
| Provider | Description | Required Env Var |
|
|
172
|
+
|----------|-------------|------------------|
|
|
173
|
+
| `anthropic` | Anthropic's official API (default) | None (uses default auth) |
|
|
174
|
+
| `glm` | Z.AI GLM models via proxy | `ZAI_API_KEY` |
|
|
175
|
+
|
|
176
|
+
## Examples
|
|
177
|
+
|
|
178
|
+
### Using Z.AI GLM Provider
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Set your API key
|
|
182
|
+
export ZAI_API_KEY="your-key-here"
|
|
183
|
+
|
|
184
|
+
# Launch Claude Code with GLM
|
|
185
|
+
./claude-toggle --provider glm
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Validating Before Launch
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Check if provider is properly configured
|
|
192
|
+
./claude-toggle --validate --provider glm
|
|
193
|
+
|
|
194
|
+
# Output: Provider 'glm' is valid and ready to use
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Verbose Mode
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# See which provider is being used
|
|
201
|
+
./claude-toggle --verbose --provider glm
|
|
202
|
+
|
|
203
|
+
# Output: Using provider: glm
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## CI/CD Setup (For Maintainers)
|
|
207
|
+
|
|
208
|
+
This project uses GitLab CI/CD to automatically publish to npm when version tags are pushed.
|
|
209
|
+
|
|
210
|
+
### npm Token Setup
|
|
211
|
+
|
|
212
|
+
> **Important**: As of December 9, 2025, npm classic tokens have been permanently revoked. You must use **Granular Access Tokens** for CI/CD automation.
|
|
213
|
+
|
|
214
|
+
#### Creating a Granular Access Token
|
|
215
|
+
|
|
216
|
+
1. Go to [npmjs.com](https://www.npmjs.com) → Sign in
|
|
217
|
+
2. Click your avatar → **Access Tokens**
|
|
218
|
+
3. Click **Generate New Token**
|
|
219
|
+
4. Configure the token:
|
|
220
|
+
- **Token name**: `gitlab-ci-publish` (or similar descriptive name)
|
|
221
|
+
- **Expiration**: Up to 90 days (maximum allowed for write tokens)
|
|
222
|
+
- **Permissions**: Read and write
|
|
223
|
+
- **Packages**: All packages or select `claude-toggle` specifically
|
|
224
|
+
- **2FA**: Enable "Bypass 2FA for automation" for CI/CD workflows
|
|
225
|
+
5. Click **Generate token** and copy it immediately (it won't be shown again)
|
|
226
|
+
|
|
227
|
+
Alternatively, create a token via CLI:
|
|
228
|
+
```bash
|
|
229
|
+
npm token create
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### GitLab CI/CD Configuration
|
|
233
|
+
|
|
234
|
+
1. Go to your GitLab project → **Settings** → **CI/CD** → **Variables**
|
|
235
|
+
2. Add a new variable:
|
|
236
|
+
- **Key**: `NPM_TOKEN`
|
|
237
|
+
- **Value**: Your granular access token
|
|
238
|
+
- **Type**: Variable
|
|
239
|
+
- **Protected**: Yes (recommended)
|
|
240
|
+
- **Masked**: Yes
|
|
241
|
+
3. If using protected variables, ensure version tags are protected:
|
|
242
|
+
- Go to **Settings** → **Repository** → **Protected tags**
|
|
243
|
+
- Add pattern: `v*`
|
|
244
|
+
|
|
245
|
+
#### Token Rotation
|
|
246
|
+
|
|
247
|
+
Since granular tokens expire after 90 days maximum, set a reminder to rotate the token before expiration. Update the `NPM_TOKEN` variable in GitLab with the new token.
|
|
248
|
+
|
|
249
|
+
### Publishing a Release
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# 1. Update version in package.json and claude-toggle
|
|
253
|
+
# 2. Commit changes
|
|
254
|
+
git add -A && git commit -m "chore: bump version to X.Y.Z"
|
|
255
|
+
|
|
256
|
+
# 3. Create and push version tag
|
|
257
|
+
git tag vX.Y.Z
|
|
258
|
+
git push origin main --tags
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The pipeline will automatically validate and publish to npm.
|
|
262
|
+
|
|
263
|
+
## Development
|
|
264
|
+
|
|
265
|
+
### Project Structure
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
claude-toggle/
|
|
269
|
+
├── claude-toggle # Main executable script
|
|
270
|
+
├── providers.json # Provider configuration
|
|
271
|
+
├── package.json # npm package manifest
|
|
272
|
+
├── bin/
|
|
273
|
+
│ ├── wrapper.js # Node.js wrapper for npm
|
|
274
|
+
│ └── postinstall.js # Post-install config setup
|
|
275
|
+
├── docs/
|
|
276
|
+
│ └── NPM_PUBLISHING.md # Publishing guide
|
|
277
|
+
├── .claude/
|
|
278
|
+
│ └── commands/
|
|
279
|
+
│ └── add-provider.md # Interactive provider wizard
|
|
280
|
+
├── CLAUDE.md # Claude Code project instructions
|
|
281
|
+
├── PROVIDERS.md # Provider configuration guide
|
|
282
|
+
├── DEVELOPER_GUIDE.md # Development documentation
|
|
283
|
+
├── LICENSE # MIT license
|
|
284
|
+
└── README.md # This file
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Running Tests
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# Test help output
|
|
291
|
+
./claude-toggle --help
|
|
292
|
+
|
|
293
|
+
# Test provider listing
|
|
294
|
+
./claude-toggle --list-providers
|
|
295
|
+
|
|
296
|
+
# Validate all providers
|
|
297
|
+
./claude-toggle --validate --provider anthropic
|
|
298
|
+
./claude-toggle --validate --provider glm # Requires ZAI_API_KEY
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Contributing
|
|
302
|
+
|
|
303
|
+
See [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md) for development setup, coding standards, and contribution guidelines.
|
|
304
|
+
|
|
305
|
+
1. Fork the repository
|
|
306
|
+
2. Create a feature branch: `git checkout -b feature/my-feature`
|
|
307
|
+
3. Make changes and test thoroughly
|
|
308
|
+
4. Commit with clear messages
|
|
309
|
+
5. Open a Pull Request
|
|
310
|
+
|
|
311
|
+
## How It Works
|
|
312
|
+
|
|
313
|
+
1. **Configuration Loading** - Finds and parses `providers.json`
|
|
314
|
+
2. **Provider Validation** - Checks required environment variables
|
|
315
|
+
3. **Environment Export** - Sets provider-specific env vars for subprocess
|
|
316
|
+
4. **Process Replacement** - Uses `exec claude` to launch Claude Code
|
|
317
|
+
|
|
318
|
+
All environment changes are scoped to the Claude Code subprocess - your shell environment remains unchanged.
|
|
319
|
+
|
|
320
|
+
## Platform Compatibility
|
|
321
|
+
|
|
322
|
+
| Platform | npm | Manual |
|
|
323
|
+
|----------|-----|--------|
|
|
324
|
+
| macOS | Full support | Full support |
|
|
325
|
+
| Linux | Full support | Full support |
|
|
326
|
+
| Windows | Via WSL/Git Bash | Via WSL/Git Bash |
|
|
327
|
+
|
|
328
|
+
### Windows Users
|
|
329
|
+
|
|
330
|
+
claude-toggle is a bash script and requires a Unix-like environment:
|
|
331
|
+
|
|
332
|
+
1. **Windows Subsystem for Linux (WSL)** - Recommended
|
|
333
|
+
```bash
|
|
334
|
+
# Install WSL, then inside WSL:
|
|
335
|
+
npm install -g claude-toggle
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
2. **Git Bash** - With Python 3 installed
|
|
339
|
+
```bash
|
|
340
|
+
npm install -g claude-toggle
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Troubleshooting
|
|
344
|
+
|
|
345
|
+
### "Configuration file not found"
|
|
346
|
+
|
|
347
|
+
Create a configuration file in one of the search paths:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
mkdir -p ~/.config/claude-providers
|
|
351
|
+
cp providers.json ~/.config/claude-providers/
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### "Missing required credentials"
|
|
355
|
+
|
|
356
|
+
The error message includes detailed setup instructions. For example, for GLM:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
export ZAI_API_KEY="your-api-key-here"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### "python3: command not found"
|
|
363
|
+
|
|
364
|
+
Install Python 3:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# macOS (using Homebrew)
|
|
368
|
+
brew install python3
|
|
369
|
+
|
|
370
|
+
# Ubuntu/Debian
|
|
371
|
+
sudo apt install python3
|
|
372
|
+
|
|
373
|
+
# Fedora
|
|
374
|
+
sudo dnf install python3
|
|
375
|
+
|
|
376
|
+
# Windows (WSL)
|
|
377
|
+
sudo apt install python3
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT License - See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script for claude-toggle npm package
|
|
5
|
+
* Sets up default configuration if not present
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
// Skip setup on Windows (package shouldn't install there, but just in case)
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
console.log('claude-toggle: Skipping setup on Windows (not supported)');
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'claude-providers');
|
|
20
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'providers.json');
|
|
21
|
+
const DEFAULT_CONFIG = path.join(__dirname, '..', 'providers.json');
|
|
22
|
+
|
|
23
|
+
// Setup configuration
|
|
24
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
25
|
+
try {
|
|
26
|
+
// Create config directory if it doesn't exist
|
|
27
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Copy default config
|
|
32
|
+
if (fs.existsSync(DEFAULT_CONFIG)) {
|
|
33
|
+
fs.copyFileSync(DEFAULT_CONFIG, CONFIG_FILE);
|
|
34
|
+
console.log(`claude-toggle: Created default config at ${CONFIG_FILE}`);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// Non-fatal: script will still work with bundled config
|
|
38
|
+
console.warn(`claude-toggle: Could not create user config: ${err.message}`);
|
|
39
|
+
console.warn('claude-toggle: Script will use bundled configuration');
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`claude-toggle: Using existing config at ${CONFIG_FILE}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Verify dependencies
|
|
46
|
+
let hasWarnings = false;
|
|
47
|
+
|
|
48
|
+
// Check bash
|
|
49
|
+
try {
|
|
50
|
+
execSync('which bash', { stdio: 'pipe' });
|
|
51
|
+
} catch {
|
|
52
|
+
console.warn('claude-toggle: Warning - bash not found in PATH');
|
|
53
|
+
hasWarnings = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check python3
|
|
57
|
+
try {
|
|
58
|
+
execSync('which python3', { stdio: 'pipe' });
|
|
59
|
+
} catch {
|
|
60
|
+
console.warn('claude-toggle: Warning - python3 not found in PATH');
|
|
61
|
+
console.warn('claude-toggle: Python 3 is required for JSON parsing');
|
|
62
|
+
hasWarnings = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check claude CLI (optional)
|
|
66
|
+
try {
|
|
67
|
+
execSync('which claude', { stdio: 'pipe' });
|
|
68
|
+
} catch {
|
|
69
|
+
console.log('claude-toggle: Note - Claude Code CLI not found');
|
|
70
|
+
console.log('claude-toggle: Install it with: npm install -g @anthropic-ai/claude-code');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!hasWarnings) {
|
|
74
|
+
console.log('claude-toggle: Installation complete!');
|
|
75
|
+
console.log('claude-toggle: Run "claude-toggle --help" to get started');
|
|
76
|
+
}
|
package/bin/wrapper.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node.js wrapper for claude-toggle bash script
|
|
5
|
+
* Provides cross-platform error handling and spawns the bash script
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
// Platform check - Windows is not supported
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
console.error('Error: claude-toggle requires a Unix-like environment (bash, Python 3).');
|
|
15
|
+
console.error('');
|
|
16
|
+
console.error('Options for Windows users:');
|
|
17
|
+
console.error(' 1. Use Windows Subsystem for Linux (WSL)');
|
|
18
|
+
console.error(' https://docs.microsoft.com/en-us/windows/wsl/install');
|
|
19
|
+
console.error(' 2. Use Git Bash with Python 3 installed');
|
|
20
|
+
console.error(' 3. Use a Linux or macOS system');
|
|
21
|
+
console.error('');
|
|
22
|
+
console.error('After setting up WSL or Git Bash, install claude-toggle inside that environment.');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Locate the bash script relative to this wrapper
|
|
27
|
+
const scriptPath = path.join(__dirname, '..', 'claude-toggle');
|
|
28
|
+
|
|
29
|
+
// Verify script exists
|
|
30
|
+
if (!fs.existsSync(scriptPath)) {
|
|
31
|
+
console.error(`Error: claude-toggle script not found at ${scriptPath}`);
|
|
32
|
+
console.error('This may indicate a corrupted installation. Try reinstalling:');
|
|
33
|
+
console.error(' npm uninstall -g claude-toggle && npm install -g claude-toggle');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Spawn the bash script with all arguments passed through
|
|
38
|
+
const child = spawn('bash', [scriptPath, ...process.argv.slice(2)], {
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
env: process.env
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('error', (err) => {
|
|
44
|
+
if (err.code === 'ENOENT') {
|
|
45
|
+
console.error('Error: bash not found.');
|
|
46
|
+
console.error('');
|
|
47
|
+
console.error('Please ensure bash is installed and in your PATH.');
|
|
48
|
+
console.error('On most Unix systems, bash is pre-installed.');
|
|
49
|
+
} else {
|
|
50
|
+
console.error(`Error spawning claude-toggle: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.on('close', (code) => {
|
|
56
|
+
process.exit(code || 0);
|
|
57
|
+
});
|
package/claude-toggle
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Claude Code Provider Toggle Script
|
|
4
|
+
# Provider-agnostic architecture for multiple Claude API providers
|
|
5
|
+
# Usage: ./claude-toggle [OPTIONS] [claude options...]
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# =============================================
|
|
10
|
+
# Script Constants
|
|
11
|
+
# =============================================
|
|
12
|
+
SCRIPT_VERSION="1.0.0"
|
|
13
|
+
SCRIPT_NAME="$(basename "$0")"
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
+
|
|
16
|
+
CONFIG_SEARCH_PATHS=(
|
|
17
|
+
"$HOME/.config/claude-providers/providers.json"
|
|
18
|
+
"$HOME/.claude-providers.json"
|
|
19
|
+
"$SCRIPT_DIR/providers.json"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# =============================================
|
|
23
|
+
# Global Variables
|
|
24
|
+
# =============================================
|
|
25
|
+
PROVIDER_NAME=""
|
|
26
|
+
CLAUDE_ARGS=()
|
|
27
|
+
ACTION="launch" # launch, list, validate, help
|
|
28
|
+
VERBOSE=false
|
|
29
|
+
|
|
30
|
+
# =============================================
|
|
31
|
+
# Usage Information
|
|
32
|
+
# =============================================
|
|
33
|
+
usage() {
|
|
34
|
+
cat << EOF
|
|
35
|
+
Usage: $SCRIPT_NAME [OPTIONS] [claude options...]
|
|
36
|
+
|
|
37
|
+
Provider-agnostic toggle script for Claude Code API providers.
|
|
38
|
+
|
|
39
|
+
OPTIONS:
|
|
40
|
+
-p, --provider <name> Select provider (default: default_provider from config)
|
|
41
|
+
-l, --list-providers List all available providers
|
|
42
|
+
-v, --verbose Enable verbose output
|
|
43
|
+
-h, --help Show this help message
|
|
44
|
+
--validate Validate provider configuration without launching
|
|
45
|
+
|
|
46
|
+
EXAMPLES:
|
|
47
|
+
# Launch with default provider (anthropic)
|
|
48
|
+
$SCRIPT_NAME
|
|
49
|
+
|
|
50
|
+
# Launch with specific provider
|
|
51
|
+
$SCRIPT_NAME --provider glm
|
|
52
|
+
|
|
53
|
+
# List available providers
|
|
54
|
+
$SCRIPT_NAME --list-providers
|
|
55
|
+
|
|
56
|
+
# Pass additional options to claude
|
|
57
|
+
$SCRIPT_NAME --provider glm --dangerously-skip-permissions
|
|
58
|
+
|
|
59
|
+
# Validate provider configuration
|
|
60
|
+
$SCRIPT_NAME --validate --provider glm
|
|
61
|
+
|
|
62
|
+
LEGACY OPTIONS:
|
|
63
|
+
--glm Alias for --provider glm (backward compatibility)
|
|
64
|
+
|
|
65
|
+
EOF
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# =============================================
|
|
69
|
+
# Configuration File Management
|
|
70
|
+
# =============================================
|
|
71
|
+
find_config_file() {
|
|
72
|
+
local config_file=""
|
|
73
|
+
for path in "${CONFIG_SEARCH_PATHS[@]}"; do
|
|
74
|
+
if [[ -f "$path" ]]; then
|
|
75
|
+
config_file="$path"
|
|
76
|
+
break
|
|
77
|
+
fi
|
|
78
|
+
done
|
|
79
|
+
echo "$config_file"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# =============================================
|
|
83
|
+
# JSON Parsing (using embedded Python)
|
|
84
|
+
# =============================================
|
|
85
|
+
parse_json() {
|
|
86
|
+
local json_file="$1"
|
|
87
|
+
local json_path="$2"
|
|
88
|
+
|
|
89
|
+
python3 -c "
|
|
90
|
+
import json, sys
|
|
91
|
+
try:
|
|
92
|
+
with open('$json_file', 'r') as f:
|
|
93
|
+
data = json.load(f)
|
|
94
|
+
keys = '$json_path'.split('.')
|
|
95
|
+
result = data
|
|
96
|
+
for key in keys:
|
|
97
|
+
if key.isdigit():
|
|
98
|
+
result = result[int(key)]
|
|
99
|
+
else:
|
|
100
|
+
result = result.get(key)
|
|
101
|
+
if result is None:
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
if isinstance(result, (str, int, float, bool)):
|
|
104
|
+
print(result)
|
|
105
|
+
elif isinstance(result, list):
|
|
106
|
+
print('\n'.join(str(x) for x in result))
|
|
107
|
+
else:
|
|
108
|
+
print(json.dumps(result))
|
|
109
|
+
except Exception as e:
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
" 2>/dev/null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# =============================================
|
|
115
|
+
# Provider Setup Instructions
|
|
116
|
+
# =============================================
|
|
117
|
+
show_setup_instructions() {
|
|
118
|
+
local missing_var="$1"
|
|
119
|
+
|
|
120
|
+
case "$missing_var" in
|
|
121
|
+
ZAI_API_KEY)
|
|
122
|
+
cat >&2 << 'EOF'
|
|
123
|
+
|
|
124
|
+
To set up your ZAI_API_KEY:
|
|
125
|
+
|
|
126
|
+
1. Get your API key from Z.AI (智谱AI):
|
|
127
|
+
- Visit: https://open.bigmodel.cn/usercenter/apikeys
|
|
128
|
+
- Sign in or create an account
|
|
129
|
+
- Click "Create API Key" to generate a new key
|
|
130
|
+
- Copy your API key
|
|
131
|
+
|
|
132
|
+
2. Set the environment variable:
|
|
133
|
+
|
|
134
|
+
# For current session only (temporary):
|
|
135
|
+
export ZAI_API_KEY="your-api-key-here"
|
|
136
|
+
|
|
137
|
+
# For permanent setup (recommended):
|
|
138
|
+
# Add this line to your shell configuration file:
|
|
139
|
+
|
|
140
|
+
# For bash (~/.bashrc or ~/.bash_profile):
|
|
141
|
+
echo 'export ZAI_API_KEY="your-api-key-here"' >> ~/.bashrc
|
|
142
|
+
source ~/.bashrc
|
|
143
|
+
|
|
144
|
+
# For zsh (~/.zshrc):
|
|
145
|
+
echo 'export ZAI_API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
146
|
+
source ~/.zshrc
|
|
147
|
+
|
|
148
|
+
# For fish (~/.config/fish/config.fish):
|
|
149
|
+
echo 'set -x ZAI_API_KEY "your-api-key-here"' >> ~/.config/fish/config.fish
|
|
150
|
+
|
|
151
|
+
3. Verify the key is set:
|
|
152
|
+
echo $ZAI_API_KEY
|
|
153
|
+
|
|
154
|
+
EOF
|
|
155
|
+
;;
|
|
156
|
+
OPENAI_API_KEY)
|
|
157
|
+
cat >&2 << 'EOF'
|
|
158
|
+
|
|
159
|
+
To set up your OPENAI_API_KEY:
|
|
160
|
+
|
|
161
|
+
1. Get your API key from OpenAI:
|
|
162
|
+
- Visit: https://platform.openai.com/api-keys
|
|
163
|
+
- Sign in to your account
|
|
164
|
+
- Click "Create new secret key"
|
|
165
|
+
- Copy your API key (it won't be shown again!)
|
|
166
|
+
|
|
167
|
+
2. Set the environment variable:
|
|
168
|
+
|
|
169
|
+
# For current session only (temporary):
|
|
170
|
+
export OPENAI_API_KEY="your-api-key-here"
|
|
171
|
+
|
|
172
|
+
# For permanent setup (recommended):
|
|
173
|
+
# Add this line to your shell configuration file:
|
|
174
|
+
|
|
175
|
+
# For bash (~/.bashrc or ~/.bash_profile):
|
|
176
|
+
echo 'export OPENAI_API_KEY="your-api-key-here"' >> ~/.bashrc
|
|
177
|
+
source ~/.bashrc
|
|
178
|
+
|
|
179
|
+
# For zsh (~/.zshrc):
|
|
180
|
+
echo 'export OPENAI_API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
181
|
+
source ~/.zshrc
|
|
182
|
+
|
|
183
|
+
# For fish (~/.config/fish/config.fish):
|
|
184
|
+
echo 'set -x OPENAI_API_KEY "your-api-key-here"' >> ~/.config/fish/config.fish
|
|
185
|
+
|
|
186
|
+
3. Verify the key is set:
|
|
187
|
+
echo $OPENAI_API_KEY
|
|
188
|
+
|
|
189
|
+
EOF
|
|
190
|
+
;;
|
|
191
|
+
ANTHROPIC_API_KEY)
|
|
192
|
+
cat >&2 << 'EOF'
|
|
193
|
+
|
|
194
|
+
To set up your ANTHROPIC_API_KEY:
|
|
195
|
+
|
|
196
|
+
1. Get your API key from Anthropic:
|
|
197
|
+
- Visit: https://console.anthropic.com/settings/keys
|
|
198
|
+
- Sign in to your account
|
|
199
|
+
- Click "Create Key"
|
|
200
|
+
- Copy your API key
|
|
201
|
+
|
|
202
|
+
2. Set the environment variable:
|
|
203
|
+
|
|
204
|
+
# For current session only (temporary):
|
|
205
|
+
export ANTHROPIC_API_KEY="your-api-key-here"
|
|
206
|
+
|
|
207
|
+
# For permanent setup (recommended):
|
|
208
|
+
# Add this line to your shell configuration file:
|
|
209
|
+
|
|
210
|
+
# For bash (~/.bashrc or ~/.bash_profile):
|
|
211
|
+
echo 'export ANTHROPIC_API_KEY="your-api-key-here"' >> ~/.bashrc
|
|
212
|
+
source ~/.bashrc
|
|
213
|
+
|
|
214
|
+
# For zsh (~/.zshrc):
|
|
215
|
+
echo 'export ANTHROPIC_API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
216
|
+
source ~/.zshrc
|
|
217
|
+
|
|
218
|
+
# For fish (~/.config/fish/config.fish):
|
|
219
|
+
echo 'set -x ANTHROPIC_API_KEY "your-api-key-here"' >> ~/.config/fish/config.fish
|
|
220
|
+
|
|
221
|
+
3. Verify the key is set:
|
|
222
|
+
echo $ANTHROPIC_API_KEY
|
|
223
|
+
|
|
224
|
+
EOF
|
|
225
|
+
;;
|
|
226
|
+
*)
|
|
227
|
+
cat >&2 << EOF
|
|
228
|
+
|
|
229
|
+
To set up your $missing_var:
|
|
230
|
+
|
|
231
|
+
1. Set the environment variable:
|
|
232
|
+
|
|
233
|
+
# For current session only (temporary):
|
|
234
|
+
export $missing_var="your-value-here"
|
|
235
|
+
|
|
236
|
+
# For permanent setup (recommended):
|
|
237
|
+
# Add this line to your shell configuration file:
|
|
238
|
+
|
|
239
|
+
# For bash (~/.bashrc or ~/.bash_profile):
|
|
240
|
+
echo 'export $missing_var="your-value-here"' >> ~/.bashrc
|
|
241
|
+
source ~/.bashrc
|
|
242
|
+
|
|
243
|
+
# For zsh (~/.zshrc):
|
|
244
|
+
echo 'export $missing_var="your-value-here"' >> ~/.zshrc
|
|
245
|
+
source ~/.zshrc
|
|
246
|
+
|
|
247
|
+
# For fish (~/.config/fish/config.fish):
|
|
248
|
+
echo 'set -x $missing_var "your-value-here"' >> ~/.config/fish/config.fish
|
|
249
|
+
|
|
250
|
+
2. Verify the value is set:
|
|
251
|
+
echo \$$missing_var
|
|
252
|
+
|
|
253
|
+
EOF
|
|
254
|
+
;;
|
|
255
|
+
esac
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# =============================================
|
|
259
|
+
# Provider Validation
|
|
260
|
+
# =============================================
|
|
261
|
+
validate_provider() {
|
|
262
|
+
local provider="$1"
|
|
263
|
+
local config_file="$2"
|
|
264
|
+
|
|
265
|
+
# Check if provider exists and is enabled
|
|
266
|
+
local enabled
|
|
267
|
+
enabled=$(parse_json "$config_file" "providers.$provider.enabled" 2>/dev/null || echo "false")
|
|
268
|
+
|
|
269
|
+
if [[ "$enabled" != "True" && "$enabled" != "true" ]]; then
|
|
270
|
+
echo "Error: Provider '$provider' not found or disabled" >&2
|
|
271
|
+
echo "" >&2
|
|
272
|
+
echo "Available providers:" >&2
|
|
273
|
+
echo " Run: $SCRIPT_NAME --list-providers" >&2
|
|
274
|
+
return 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Check required environment variables
|
|
278
|
+
local required_vars
|
|
279
|
+
required_vars=$(parse_json "$config_file" "providers.$provider.validation.required_env_vars" 2>/dev/null || echo "")
|
|
280
|
+
|
|
281
|
+
if [[ -n "$required_vars" ]]; then
|
|
282
|
+
local missing_vars=()
|
|
283
|
+
while IFS= read -r var; do
|
|
284
|
+
[[ -z "$var" ]] && continue
|
|
285
|
+
if [[ -z "${!var}" ]]; then
|
|
286
|
+
missing_vars+=("$var")
|
|
287
|
+
fi
|
|
288
|
+
done <<< "$required_vars"
|
|
289
|
+
|
|
290
|
+
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
|
291
|
+
echo "Error: Missing required credentials for provider '$provider'" >&2
|
|
292
|
+
echo "" >&2
|
|
293
|
+
for var in "${missing_vars[@]}"; do
|
|
294
|
+
echo " Missing: $var" >&2
|
|
295
|
+
done
|
|
296
|
+
echo "" >&2
|
|
297
|
+
show_setup_instructions "${missing_vars[0]}"
|
|
298
|
+
return 1
|
|
299
|
+
fi
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
return 0
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# =============================================
|
|
306
|
+
# List Providers
|
|
307
|
+
# =============================================
|
|
308
|
+
list_providers() {
|
|
309
|
+
local config_file="$1"
|
|
310
|
+
local default_provider
|
|
311
|
+
local json_output
|
|
312
|
+
|
|
313
|
+
default_provider=$(parse_json "$config_file" "default_provider" 2>/dev/null || echo "")
|
|
314
|
+
|
|
315
|
+
echo "Available providers:"
|
|
316
|
+
echo "===================="
|
|
317
|
+
|
|
318
|
+
# Get provider names using Python
|
|
319
|
+
json_output=$(parse_json "$config_file" "providers" 2>/dev/null || echo "{}")
|
|
320
|
+
|
|
321
|
+
if [[ -z "$json_output" || "$json_output" == "{}" ]]; then
|
|
322
|
+
echo "No providers found in configuration"
|
|
323
|
+
return 1
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Parse provider names
|
|
327
|
+
python3 << PYTHON_SCRIPT
|
|
328
|
+
import json, sys
|
|
329
|
+
try:
|
|
330
|
+
data = json.loads(open('$config_file').read())
|
|
331
|
+
providers = data.get('providers', {})
|
|
332
|
+
default = data.get('default_provider', '')
|
|
333
|
+
|
|
334
|
+
for name, config in providers.items():
|
|
335
|
+
enabled = config.get('enabled', False)
|
|
336
|
+
if not enabled:
|
|
337
|
+
continue
|
|
338
|
+
desc = config.get('description', '')
|
|
339
|
+
marker = ' * (default)' if name == default else ''
|
|
340
|
+
print(f" {name:<20} {desc}{marker}")
|
|
341
|
+
except Exception as e:
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
PYTHON_SCRIPT
|
|
344
|
+
|
|
345
|
+
echo ""
|
|
346
|
+
echo "* = default provider"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
# =============================================
|
|
350
|
+
# Export Provider Environment Variables
|
|
351
|
+
# =============================================
|
|
352
|
+
export_provider_vars() {
|
|
353
|
+
local provider="$1"
|
|
354
|
+
local config_file="$2"
|
|
355
|
+
|
|
356
|
+
# For anthropic/default, no env vars needed
|
|
357
|
+
if [[ "$provider" == "anthropic" ]]; then
|
|
358
|
+
return 0
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
# Get env_vars as JSON
|
|
362
|
+
local env_vars_json
|
|
363
|
+
env_vars_json=$(parse_json "$config_file" "providers.$provider.env_vars" 2>/dev/null || echo "{}")
|
|
364
|
+
|
|
365
|
+
if [[ "$env_vars_json" == "{}" || "$env_vars_json" == "null" ]]; then
|
|
366
|
+
return 0
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# Parse and export each variable using Python
|
|
370
|
+
python3 << PYTHON_SCRIPT
|
|
371
|
+
import json, sys, os
|
|
372
|
+
try:
|
|
373
|
+
data = json.loads(open('$config_file').read())
|
|
374
|
+
env_vars = data.get('providers', {}).get('$provider', {}).get('env_vars', {})
|
|
375
|
+
|
|
376
|
+
for key, val in env_vars.items():
|
|
377
|
+
value = val.get('value', '')
|
|
378
|
+
# Expand \${VAR} references
|
|
379
|
+
if value.startswith('\${') and value.endswith('}'):
|
|
380
|
+
env_var = value[2:-1]
|
|
381
|
+
value = os.environ.get(env_var, '')
|
|
382
|
+
if value:
|
|
383
|
+
print(f"export {key}=\"{value}\"")
|
|
384
|
+
except Exception as e:
|
|
385
|
+
sys.exit(1)
|
|
386
|
+
PYTHON_SCRIPT
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# =============================================
|
|
390
|
+
# Main Script Logic
|
|
391
|
+
# =============================================
|
|
392
|
+
main() {
|
|
393
|
+
# Parse command-line arguments
|
|
394
|
+
while [[ $# -gt 0 ]]; do
|
|
395
|
+
case $1 in
|
|
396
|
+
-p|--provider)
|
|
397
|
+
PROVIDER_NAME="$2"
|
|
398
|
+
shift 2
|
|
399
|
+
;;
|
|
400
|
+
-l|--list-providers)
|
|
401
|
+
ACTION="list"
|
|
402
|
+
shift
|
|
403
|
+
;;
|
|
404
|
+
-v|--verbose)
|
|
405
|
+
VERBOSE=true
|
|
406
|
+
shift
|
|
407
|
+
;;
|
|
408
|
+
-h|--help)
|
|
409
|
+
ACTION="help"
|
|
410
|
+
shift
|
|
411
|
+
;;
|
|
412
|
+
--validate)
|
|
413
|
+
ACTION="validate"
|
|
414
|
+
shift
|
|
415
|
+
;;
|
|
416
|
+
# Legacy backward compatibility
|
|
417
|
+
--glm)
|
|
418
|
+
PROVIDER_NAME="glm"
|
|
419
|
+
shift
|
|
420
|
+
;;
|
|
421
|
+
# Passthrough to claude
|
|
422
|
+
*)
|
|
423
|
+
CLAUDE_ARGS+=("$1")
|
|
424
|
+
shift
|
|
425
|
+
;;
|
|
426
|
+
esac
|
|
427
|
+
done
|
|
428
|
+
|
|
429
|
+
# Handle help action
|
|
430
|
+
if [[ "$ACTION" == "help" ]]; then
|
|
431
|
+
usage
|
|
432
|
+
exit 0
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
# Find configuration file
|
|
436
|
+
local config_file
|
|
437
|
+
config_file=$(find_config_file)
|
|
438
|
+
|
|
439
|
+
# Handle list action
|
|
440
|
+
if [[ "$ACTION" == "list" ]]; then
|
|
441
|
+
if [[ -z "$config_file" ]]; then
|
|
442
|
+
echo "Error: Configuration file not found" >&2
|
|
443
|
+
echo "Searched paths:" >&2
|
|
444
|
+
printf " - %s\n" "${CONFIG_SEARCH_PATHS[@]}" >&2
|
|
445
|
+
exit 1
|
|
446
|
+
fi
|
|
447
|
+
list_providers "$config_file"
|
|
448
|
+
exit 0
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
# Validate config file exists for other actions
|
|
452
|
+
if [[ -z "$config_file" ]]; then
|
|
453
|
+
echo "Error: Configuration file not found" >&2
|
|
454
|
+
echo "Searched paths:" >&2
|
|
455
|
+
printf " - %s\n" "${CONFIG_SEARCH_PATHS[@]}" >&2
|
|
456
|
+
echo "" >&2
|
|
457
|
+
echo "Please create a configuration file or run: $SCRIPT_NAME --help" >&2
|
|
458
|
+
exit 1
|
|
459
|
+
fi
|
|
460
|
+
|
|
461
|
+
# Determine provider name
|
|
462
|
+
if [[ -z "$PROVIDER_NAME" ]]; then
|
|
463
|
+
PROVIDER_NAME=$(parse_json "$config_file" "default_provider" 2>/dev/null || echo "anthropic")
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Validate provider
|
|
467
|
+
if ! validate_provider "$PROVIDER_NAME" "$config_file"; then
|
|
468
|
+
exit 1
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
# Handle validate action
|
|
472
|
+
if [[ "$ACTION" == "validate" ]]; then
|
|
473
|
+
echo "Provider '$PROVIDER_NAME' is valid and ready to use"
|
|
474
|
+
exit 0
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
# Export provider environment variables
|
|
478
|
+
if [[ "$VERBOSE" == true ]]; then
|
|
479
|
+
echo "Using provider: $PROVIDER_NAME" >&2
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
# Export the environment variables
|
|
483
|
+
eval "$(export_provider_vars "$PROVIDER_NAME" "$config_file")"
|
|
484
|
+
|
|
485
|
+
# Launch claude with passthrough arguments
|
|
486
|
+
exec claude "${CLAUDE_ARGS[@]}"
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# Run main function
|
|
490
|
+
main "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sekora_ai/claude-toggle",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Provider-agnostic toggle for Claude Code API providers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"anthropic",
|
|
8
|
+
"api",
|
|
9
|
+
"provider",
|
|
10
|
+
"toggle",
|
|
11
|
+
"cli",
|
|
12
|
+
"glm",
|
|
13
|
+
"ai"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/seansekora/claude-toggle",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/seansekora/claude-toggle/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/seansekora/claude-toggle.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Sean Sekora",
|
|
25
|
+
"bin": {
|
|
26
|
+
"claude-toggle": "./bin/wrapper.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/",
|
|
30
|
+
"claude-toggle",
|
|
31
|
+
"providers.json",
|
|
32
|
+
"PROVIDERS.md",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"os": [
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux",
|
|
38
|
+
"!win32"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=14.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"postinstall": "node bin/postinstall.js",
|
|
45
|
+
"test": "bash claude-toggle --help && bash claude-toggle --list-providers"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/providers.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"default_provider": "anthropic",
|
|
5
|
+
"providers": {
|
|
6
|
+
"anthropic": {
|
|
7
|
+
"description": "Anthropic's official API",
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"env_vars": {},
|
|
10
|
+
"validation": {}
|
|
11
|
+
},
|
|
12
|
+
"glm": {
|
|
13
|
+
"description": "Z.AI GLM models via proxy",
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"env_vars": {
|
|
16
|
+
"ANTHROPIC_AUTH_TOKEN": {
|
|
17
|
+
"value": "${ZAI_API_KEY}",
|
|
18
|
+
"required": true
|
|
19
|
+
},
|
|
20
|
+
"ANTHROPIC_BASE_URL": {
|
|
21
|
+
"value": "https://api.z.ai/api/anthropic"
|
|
22
|
+
},
|
|
23
|
+
"API_TIMEOUT_MS": {
|
|
24
|
+
"value": "3000000"
|
|
25
|
+
},
|
|
26
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": {
|
|
27
|
+
"value": "glm-4.5-air"
|
|
28
|
+
},
|
|
29
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": {
|
|
30
|
+
"value": "glm-4.7"
|
|
31
|
+
},
|
|
32
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": {
|
|
33
|
+
"value": "glm-4.7"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"validation": {
|
|
37
|
+
"required_env_vars": ["ZAI_API_KEY"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|