@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 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
+ }