ccconfig 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/README.md +368 -0
- package/ccconfig.js +942 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Claude Code Configuration Manager
|
|
2
|
+
|
|
3
|
+
Quickly switch between different claude-code providers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
# Switch to work configuration during work hours
|
|
8
|
+
ccconfig use company
|
|
9
|
+
|
|
10
|
+
# Switch back to personal configuration after work
|
|
11
|
+
ccconfig use personal
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install from npm (recommended)
|
|
20
|
+
npm install -g ccconfig
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### ENV Mode (Recommended, Default)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. Configure Shell auto-loading (see below)
|
|
27
|
+
|
|
28
|
+
# 2. Add configuration (interactive mode)
|
|
29
|
+
ccconfig add
|
|
30
|
+
# Follow the prompts to enter:
|
|
31
|
+
# - Name
|
|
32
|
+
# - ANTHROPIC_BASE_URL
|
|
33
|
+
# - ANTHROPIC_AUTH_TOKEN
|
|
34
|
+
# - ANTHROPIC_API_KEY
|
|
35
|
+
# - Description
|
|
36
|
+
|
|
37
|
+
# 3. Switch configuration
|
|
38
|
+
ccconfig use work
|
|
39
|
+
|
|
40
|
+
# 4. Restart Shell or apply immediately
|
|
41
|
+
eval $(ccconfig env bash) # or use the detected command from output
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Settings Mode
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. Switch to settings mode
|
|
48
|
+
ccconfig mode settings
|
|
49
|
+
|
|
50
|
+
# 2. Add configuration (interactive mode)
|
|
51
|
+
ccconfig add
|
|
52
|
+
# Follow the prompts to configure
|
|
53
|
+
|
|
54
|
+
# 3. Switch configuration
|
|
55
|
+
ccconfig use work
|
|
56
|
+
|
|
57
|
+
# 4. Restart Claude Code
|
|
58
|
+
# Configuration is now active!
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### ENV Mode Shell Configuration
|
|
62
|
+
|
|
63
|
+
Configure once by adding to your Shell startup files:
|
|
64
|
+
|
|
65
|
+
**Fish** (`~/.config/fish/config.fish`):
|
|
66
|
+
```fish
|
|
67
|
+
# Load Claude Code environment variables
|
|
68
|
+
set -l claude_env ~/.config/claude-code/current.env
|
|
69
|
+
if test -f $claude_env
|
|
70
|
+
for line in (cat $claude_env)
|
|
71
|
+
set -l parts (string split '=' $line)
|
|
72
|
+
set -gx $parts[1] $parts[2]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Bash** (`~/.bashrc`):
|
|
78
|
+
```bash
|
|
79
|
+
# Load Claude Code environment variables
|
|
80
|
+
if [ -f ~/.config/claude-code/current.env ]; then
|
|
81
|
+
export $(grep -v '^#' ~/.config/claude-code/current.env | xargs)
|
|
82
|
+
fi
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Zsh** (`~/.zshrc`):
|
|
86
|
+
```zsh
|
|
87
|
+
# Load Claude Code environment variables
|
|
88
|
+
if [ -f ~/.config/claude-code/current.env ]; then
|
|
89
|
+
export $(grep -v '^#' ~/.config/claude-code/current.env | xargs)
|
|
90
|
+
fi
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**PowerShell** (`$PROFILE`):
|
|
94
|
+
```powershell
|
|
95
|
+
# Load Claude Code environment variables
|
|
96
|
+
$claudeEnv = "$env:USERPROFILE\.config\claude-code\current.env"
|
|
97
|
+
if (Test-Path $claudeEnv) {
|
|
98
|
+
Get-Content $claudeEnv | ForEach-Object {
|
|
99
|
+
if ($_ -match '^([^=]+)=(.*)$') {
|
|
100
|
+
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Command Reference
|
|
107
|
+
|
|
108
|
+
### Basic Commands
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Run without command (defaults to list)
|
|
112
|
+
ccconfig
|
|
113
|
+
|
|
114
|
+
# List all configurations
|
|
115
|
+
ccconfig list
|
|
116
|
+
|
|
117
|
+
# Add new configuration (interactive mode only, auto-creates config file on first use)
|
|
118
|
+
ccconfig add
|
|
119
|
+
|
|
120
|
+
# Switch configuration
|
|
121
|
+
ccconfig use <name>
|
|
122
|
+
|
|
123
|
+
# Remove configuration
|
|
124
|
+
ccconfig remove <name>
|
|
125
|
+
|
|
126
|
+
# View current status (shows all configuration sources)
|
|
127
|
+
ccconfig current
|
|
128
|
+
ccconfig current --show-secret # Show full token
|
|
129
|
+
|
|
130
|
+
# Show configuration file path
|
|
131
|
+
ccconfig edit
|
|
132
|
+
|
|
133
|
+
# View version
|
|
134
|
+
ccconfig --version # or -V
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Mode Management
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# View current mode
|
|
141
|
+
ccconfig mode
|
|
142
|
+
|
|
143
|
+
# Switch to settings mode
|
|
144
|
+
ccconfig mode settings
|
|
145
|
+
|
|
146
|
+
# Switch to env mode
|
|
147
|
+
ccconfig mode env
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### ENV Mode Specific
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Apply immediately in current Shell (env mode)
|
|
154
|
+
eval $(ccconfig env bash) # Bash/Zsh
|
|
155
|
+
ccconfig env fish | source # Fish
|
|
156
|
+
ccconfig env pwsh | iex # PowerShell
|
|
157
|
+
|
|
158
|
+
# Output .env format
|
|
159
|
+
ccconfig env dotenv > .env
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Configuration File Locations
|
|
163
|
+
|
|
164
|
+
- **Configuration List**: `~/.config/claude-code/profiles.json`
|
|
165
|
+
- **Claude Settings**: `~/.claude/settings.json`
|
|
166
|
+
- **Environment Variables File**: `~/.config/claude-code/current.env`
|
|
167
|
+
- **Mode Settings**: `~/.config/claude-code/mode`
|
|
168
|
+
|
|
169
|
+
## Configuration Example
|
|
170
|
+
|
|
171
|
+
`~/.config/claude-code/profiles.json`:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"profiles": {
|
|
176
|
+
"work": {
|
|
177
|
+
"env": {
|
|
178
|
+
"ANTHROPIC_BASE_URL": "https://api-proxy.company.com",
|
|
179
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-auth-work-xxxxx",
|
|
180
|
+
"ANTHROPIC_API_KEY": "sk-key-work-xxxxx"
|
|
181
|
+
},
|
|
182
|
+
"description": "Work account"
|
|
183
|
+
},
|
|
184
|
+
"personal": {
|
|
185
|
+
"env": {
|
|
186
|
+
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
|
187
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-ant-personal-xxxxx",
|
|
188
|
+
"ANTHROPIC_API_KEY": "sk-ant-personal-xxxxx"
|
|
189
|
+
},
|
|
190
|
+
"description": "Personal account"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Advanced Usage
|
|
197
|
+
|
|
198
|
+
### Quick Aliases
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Add to ~/.bashrc or ~/.zshrc
|
|
202
|
+
alias ccs='ccconfig'
|
|
203
|
+
alias ccs-use='ccconfig use'
|
|
204
|
+
alias ccs-list='ccconfig list'
|
|
205
|
+
alias ccs-current='ccconfig current'
|
|
206
|
+
|
|
207
|
+
# Fish (~/.config/fish/config.fish)
|
|
208
|
+
abbr ccs 'ccconfig'
|
|
209
|
+
abbr ccs-use 'ccconfig use'
|
|
210
|
+
abbr ccs-list 'ccconfig list'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Project-Level Configuration
|
|
214
|
+
|
|
215
|
+
For specific projects, you can export .env files:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Export to project directory
|
|
219
|
+
cd my-project
|
|
220
|
+
ccconfig use project-config
|
|
221
|
+
ccconfig env dotenv > .env
|
|
222
|
+
|
|
223
|
+
# Use project configuration
|
|
224
|
+
source .env
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Backup and Synchronization
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Backup configuration
|
|
231
|
+
cp ~/.config/claude-code/profiles.json ~/backup/claude-profiles.json
|
|
232
|
+
|
|
233
|
+
# Sync to new machine
|
|
234
|
+
scp ~/backup/claude-profiles.json new-machine:~/.config/claude-code/
|
|
235
|
+
|
|
236
|
+
# Or use version control (be careful with security!)
|
|
237
|
+
cd ~/.config/claude-code
|
|
238
|
+
git init
|
|
239
|
+
echo "*.env" >> .gitignore
|
|
240
|
+
git add profiles.json
|
|
241
|
+
git commit -m "Claude Code profiles"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Troubleshooting
|
|
245
|
+
|
|
246
|
+
### Configuration Not Taking Effect
|
|
247
|
+
|
|
248
|
+
**Settings Mode**:
|
|
249
|
+
1. Check if configuration is written correctly: `ccconfig current`
|
|
250
|
+
2. Confirm Claude Code has been restarted
|
|
251
|
+
3. Check the `env` field in `~/.claude/settings.json`
|
|
252
|
+
|
|
253
|
+
**ENV Mode**:
|
|
254
|
+
1. Check environment variables file: `cat ~/.config/claude-code/current.env`
|
|
255
|
+
2. Confirm Shell configuration is correct: `cat ~/.bashrc | grep claude`
|
|
256
|
+
3. Restart Shell or use `eval $(ccconfig env bash)`
|
|
257
|
+
4. Check process environment variables: `ccconfig current`
|
|
258
|
+
|
|
259
|
+
### Configuration Lost After Mode Switch
|
|
260
|
+
|
|
261
|
+
Switching modes does not affect saved configurations, only changes how configurations are applied. After switching, you need to `use` once more:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
ccconfig mode env # Switch to env mode
|
|
265
|
+
ccconfig use work # Reapply configuration
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### File Permission Issues
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Fix configuration file permissions
|
|
272
|
+
chmod 600 ~/.config/claude-code/profiles.json
|
|
273
|
+
chmod 600 ~/.claude/settings.json
|
|
274
|
+
chmod 600 ~/.config/claude-code/current.env
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Security Considerations
|
|
278
|
+
|
|
279
|
+
1. **File Permissions**: The tool automatically sets configuration files to 600 permissions (owner read/write only)
|
|
280
|
+
|
|
281
|
+
2. **Sensitive Information**:
|
|
282
|
+
- API keys are hidden by default, use `--show-secret` to view full values
|
|
283
|
+
- Do not commit configuration files to public repositories
|
|
284
|
+
- Use `.gitignore` to exclude sensitive files
|
|
285
|
+
|
|
286
|
+
3. **Environment Variables**: ENV mode environment variables are inherited by child processes, be mindful of security
|
|
287
|
+
|
|
288
|
+
4. **Version Control**: If version controlling configurations, use encryption or private repositories
|
|
289
|
+
|
|
290
|
+
## Frequently Asked Questions
|
|
291
|
+
|
|
292
|
+
**Q: Which is better, Settings mode or ENV mode?**
|
|
293
|
+
|
|
294
|
+
A:
|
|
295
|
+
- **ENV mode** is recommended (default, better cross-shell support, instant apply)
|
|
296
|
+
- If you prefer not to configure shell startup files, **Settings mode** can be simpler (only needs Claude Code restart)
|
|
297
|
+
|
|
298
|
+
**Q: Can I use both modes simultaneously?**
|
|
299
|
+
|
|
300
|
+
A: Not recommended. Claude Code reads configuration based on priority:
|
|
301
|
+
- Settings mode: Reads directly from `settings.json`
|
|
302
|
+
- ENV mode: Reads from environment variables
|
|
303
|
+
|
|
304
|
+
Using both simultaneously may cause confusion.
|
|
305
|
+
|
|
306
|
+
**Q: How to use on Windows?**
|
|
307
|
+
|
|
308
|
+
A: Fully supported on Windows:
|
|
309
|
+
- Configuration file location: `%USERPROFILE%\.config\claude-code\`
|
|
310
|
+
- Settings mode requires no additional configuration
|
|
311
|
+
- ENV mode uses PowerShell configuration
|
|
312
|
+
|
|
313
|
+
**Q: Do I need to restart after switching configurations?**
|
|
314
|
+
|
|
315
|
+
A:
|
|
316
|
+
- **Settings mode**: Need to restart Claude Code
|
|
317
|
+
- **ENV mode**: Need to restart Shell (or use `env` command for immediate effect)
|
|
318
|
+
|
|
319
|
+
**Q: Can I export configurations for team use?**
|
|
320
|
+
|
|
321
|
+
A: Yes, but be careful:
|
|
322
|
+
```bash
|
|
323
|
+
# Export configuration structure (excluding API keys)
|
|
324
|
+
cat ~/.config/claude-code/profiles.json | \
|
|
325
|
+
jq '.profiles | map_values({baseUrl, description})' > team-config.json
|
|
326
|
+
|
|
327
|
+
# Team members manually add their own API keys after importing
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Development
|
|
331
|
+
|
|
332
|
+
### Project Structure
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
.
|
|
336
|
+
├── ccconfig.js # Core script
|
|
337
|
+
├── package.json # npm configuration
|
|
338
|
+
├── README.md # This document
|
|
339
|
+
└── .gitignore # Git ignore file
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Testing
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Test version output
|
|
346
|
+
node ccconfig.js --version
|
|
347
|
+
|
|
348
|
+
# Test adding configuration (interactive only)
|
|
349
|
+
node ccconfig.js add
|
|
350
|
+
|
|
351
|
+
# Test listing
|
|
352
|
+
node ccconfig.js list
|
|
353
|
+
|
|
354
|
+
# Test switching
|
|
355
|
+
node ccconfig.js use test
|
|
356
|
+
|
|
357
|
+
# Test status viewing
|
|
358
|
+
node ccconfig.js current
|
|
359
|
+
node ccconfig.js current --show-secret
|
|
360
|
+
|
|
361
|
+
# Test mode switching
|
|
362
|
+
node ccconfig.js mode
|
|
363
|
+
node ccconfig.js mode env
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
MIT
|
package/ccconfig.js
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
// Configuration file paths
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'claude-code');
|
|
10
|
+
const PROFILES_FILE = path.join(CONFIG_DIR, 'profiles.json');
|
|
11
|
+
const CLAUDE_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
12
|
+
const ENV_FILE = path.join(CONFIG_DIR, 'current.env');
|
|
13
|
+
const MODE_FILE = path.join(CONFIG_DIR, 'mode');
|
|
14
|
+
|
|
15
|
+
// Default modes
|
|
16
|
+
const MODE_SETTINGS = 'settings'; // Directly modify ~/.claude/settings.json
|
|
17
|
+
const MODE_ENV = 'env'; // Use environment variable files
|
|
18
|
+
|
|
19
|
+
let PACKAGE_VERSION = 'unknown';
|
|
20
|
+
try {
|
|
21
|
+
const packageJson = require('./package.json');
|
|
22
|
+
if (packageJson && typeof packageJson.version === 'string') {
|
|
23
|
+
PACKAGE_VERSION = packageJson.version;
|
|
24
|
+
}
|
|
25
|
+
} catch (_) {
|
|
26
|
+
// Keep default 'unknown' when package.json is unavailable
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensure directory exists
|
|
31
|
+
*/
|
|
32
|
+
function ensureDir(dir) {
|
|
33
|
+
if (!fs.existsSync(dir)) {
|
|
34
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load configuration file
|
|
40
|
+
*/
|
|
41
|
+
function loadProfiles() {
|
|
42
|
+
try {
|
|
43
|
+
if (!fs.existsSync(PROFILES_FILE)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const content = fs.readFileSync(PROFILES_FILE, 'utf-8');
|
|
47
|
+
return JSON.parse(content);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`Error: Unable to read configuration file: ${error.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Save configuration file
|
|
56
|
+
*/
|
|
57
|
+
function saveProfiles(profiles) {
|
|
58
|
+
try {
|
|
59
|
+
ensureDir(CONFIG_DIR);
|
|
60
|
+
fs.writeFileSync(PROFILES_FILE, JSON.stringify(profiles, null, 2), 'utf-8');
|
|
61
|
+
|
|
62
|
+
// Set file permissions to owner read/write only (600)
|
|
63
|
+
if (os.platform() !== 'win32') {
|
|
64
|
+
fs.chmodSync(PROFILES_FILE, 0o600);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Error: Unable to save configuration file: ${error.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Load Claude Code settings.json
|
|
74
|
+
*/
|
|
75
|
+
function loadClaudeSettings() {
|
|
76
|
+
try {
|
|
77
|
+
if (!fs.existsSync(CLAUDE_SETTINGS)) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS, 'utf-8');
|
|
81
|
+
return JSON.parse(content);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(
|
|
84
|
+
`Warning: Unable to read Claude settings.json: ${error.message}`);
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Save Claude Code settings.json
|
|
91
|
+
*/
|
|
92
|
+
function saveClaudeSettings(settings) {
|
|
93
|
+
try {
|
|
94
|
+
ensureDir(path.dirname(CLAUDE_SETTINGS));
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf-8');
|
|
97
|
+
|
|
98
|
+
if (os.platform() !== 'win32') {
|
|
99
|
+
fs.chmodSync(CLAUDE_SETTINGS, 0o600);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(
|
|
103
|
+
`Error: Unable to save Claude settings.json: ${error.message}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get current mode
|
|
110
|
+
*/
|
|
111
|
+
function getMode() {
|
|
112
|
+
try {
|
|
113
|
+
if (fs.existsSync(MODE_FILE)) {
|
|
114
|
+
const mode = fs.readFileSync(MODE_FILE, 'utf-8').trim();
|
|
115
|
+
return mode === MODE_SETTINGS ? MODE_SETTINGS : MODE_ENV;
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Ignore error, use default mode
|
|
119
|
+
}
|
|
120
|
+
return MODE_ENV;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set mode
|
|
125
|
+
*/
|
|
126
|
+
function setMode(mode) {
|
|
127
|
+
try {
|
|
128
|
+
ensureDir(CONFIG_DIR);
|
|
129
|
+
fs.writeFileSync(MODE_FILE, mode, 'utf-8');
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Error: Unable to save mode settings: ${error.message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Update Claude Code environment variable configuration (settings mode)
|
|
138
|
+
*/
|
|
139
|
+
function updateClaudeSettings(envVars) {
|
|
140
|
+
const settings = loadClaudeSettings();
|
|
141
|
+
|
|
142
|
+
if (!settings.env) {
|
|
143
|
+
settings.env = {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Clear old related environment variables
|
|
147
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
148
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
149
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
150
|
+
|
|
151
|
+
// Set new environment variables
|
|
152
|
+
Object.assign(settings.env, envVars);
|
|
153
|
+
|
|
154
|
+
saveClaudeSettings(settings);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Write environment variable file (env mode)
|
|
159
|
+
*/
|
|
160
|
+
function writeEnvFile(envVars) {
|
|
161
|
+
try {
|
|
162
|
+
ensureDir(CONFIG_DIR);
|
|
163
|
+
const lines =
|
|
164
|
+
Object.entries(envVars).map(([key, value]) => `${key}=${value}`);
|
|
165
|
+
const content = lines.join('\n') + '\n';
|
|
166
|
+
fs.writeFileSync(ENV_FILE, content, 'utf-8');
|
|
167
|
+
|
|
168
|
+
if (os.platform() !== 'win32') {
|
|
169
|
+
fs.chmodSync(ENV_FILE, 0o600);
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(
|
|
173
|
+
`Error: Unable to write environment variable file: ${error.message}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Read environment variable file
|
|
180
|
+
*/
|
|
181
|
+
function readEnvFile() {
|
|
182
|
+
try {
|
|
183
|
+
if (!fs.existsSync(ENV_FILE)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const content = fs.readFileSync(ENV_FILE, 'utf-8');
|
|
187
|
+
const env = {};
|
|
188
|
+
content.split('\n').forEach(line => {
|
|
189
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
190
|
+
if (match) {
|
|
191
|
+
env[match[1]] = match[2];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return env;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get currently available environment variables (automatically select source
|
|
202
|
+
* based on mode)
|
|
203
|
+
*/
|
|
204
|
+
function getActiveEnvVars() {
|
|
205
|
+
const mode = getMode();
|
|
206
|
+
|
|
207
|
+
if (mode === MODE_ENV) {
|
|
208
|
+
return readEnvFile();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const settings = loadClaudeSettings();
|
|
212
|
+
if (settings && settings.env && Object.keys(settings.env).length > 0) {
|
|
213
|
+
return settings.env;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const envVars = readEnvFile();
|
|
217
|
+
if (envVars && Object.keys(envVars).length > 0) {
|
|
218
|
+
return envVars;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get currently active configuration
|
|
226
|
+
*/
|
|
227
|
+
function getCurrentProfile() {
|
|
228
|
+
const mode = getMode();
|
|
229
|
+
const profiles = loadProfiles();
|
|
230
|
+
|
|
231
|
+
if (!profiles) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let currentEnv;
|
|
236
|
+
|
|
237
|
+
if (mode === MODE_SETTINGS) {
|
|
238
|
+
const settings = loadClaudeSettings();
|
|
239
|
+
if (!settings.env) return null;
|
|
240
|
+
currentEnv = settings.env;
|
|
241
|
+
} else {
|
|
242
|
+
const env = readEnvFile();
|
|
243
|
+
if (!env) return null;
|
|
244
|
+
currentEnv = env;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Compare environment variables for matches
|
|
248
|
+
for (const [name, profile] of Object.entries(profiles.profiles)) {
|
|
249
|
+
if (!profile.env) continue;
|
|
250
|
+
|
|
251
|
+
const profileEnv = profile.env;
|
|
252
|
+
let matched = true;
|
|
253
|
+
|
|
254
|
+
// Check if ANTHROPIC_BASE_URL matches
|
|
255
|
+
if (profileEnv.ANTHROPIC_BASE_URL !== currentEnv.ANTHROPIC_BASE_URL) {
|
|
256
|
+
matched = false;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if authentication token matches (supports both fields)
|
|
261
|
+
const profileAuth =
|
|
262
|
+
profileEnv.ANTHROPIC_AUTH_TOKEN || profileEnv.ANTHROPIC_API_KEY;
|
|
263
|
+
const currentAuth =
|
|
264
|
+
currentEnv.ANTHROPIC_AUTH_TOKEN || currentEnv.ANTHROPIC_API_KEY;
|
|
265
|
+
|
|
266
|
+
if (profileAuth !== currentAuth) {
|
|
267
|
+
matched = false;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (matched) {
|
|
272
|
+
return name;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Initialize configuration file (auto-called when needed)
|
|
281
|
+
*/
|
|
282
|
+
function initIfNeeded() {
|
|
283
|
+
if (!fs.existsSync(PROFILES_FILE)) {
|
|
284
|
+
const emptyProfiles = {profiles: {}};
|
|
285
|
+
try {
|
|
286
|
+
ensureDir(CONFIG_DIR);
|
|
287
|
+
saveProfiles(emptyProfiles);
|
|
288
|
+
console.log(`✓ Configuration file created: ${PROFILES_FILE}`);
|
|
289
|
+
console.log('');
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(
|
|
292
|
+
`Error: Unable to create configuration file: ${error.message}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* List all configurations
|
|
300
|
+
*/
|
|
301
|
+
function list() {
|
|
302
|
+
const profiles = loadProfiles();
|
|
303
|
+
|
|
304
|
+
if (!profiles || !profiles.profiles ||
|
|
305
|
+
Object.keys(profiles.profiles).length === 0) {
|
|
306
|
+
console.log('No configurations found.');
|
|
307
|
+
console.log('');
|
|
308
|
+
console.log('Add your first configuration:');
|
|
309
|
+
console.log(' ccconfig add work');
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(
|
|
312
|
+
'The command will guide you through configuration step by step.');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const currentProfile = getCurrentProfile();
|
|
317
|
+
|
|
318
|
+
console.log('Available configurations:\n');
|
|
319
|
+
|
|
320
|
+
for (const [name, profile] of Object.entries(profiles.profiles)) {
|
|
321
|
+
const isCurrent = name === currentProfile ? ' ← current' : '';
|
|
322
|
+
console.log(` ${name}${isCurrent}`);
|
|
323
|
+
if (profile.env && profile.env.ANTHROPIC_BASE_URL) {
|
|
324
|
+
console.log(` URL: ${profile.env.ANTHROPIC_BASE_URL}`);
|
|
325
|
+
}
|
|
326
|
+
if (profile.description) {
|
|
327
|
+
console.log(` Description: ${profile.description}`);
|
|
328
|
+
}
|
|
329
|
+
console.log('');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (currentProfile) {
|
|
333
|
+
console.log(`Currently active: ${currentProfile}`);
|
|
334
|
+
} else {
|
|
335
|
+
const settings = loadClaudeSettings();
|
|
336
|
+
if (settings.env && settings.env.ANTHROPIC_BASE_URL) {
|
|
337
|
+
console.log(
|
|
338
|
+
'Currently using custom configuration (not in configuration list)');
|
|
339
|
+
console.log(` URL: ${settings.env.ANTHROPIC_BASE_URL}`);
|
|
340
|
+
} else {
|
|
341
|
+
console.log('Claude Code environment variables not configured yet');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Add new configuration
|
|
348
|
+
*/
|
|
349
|
+
async function add(name) {
|
|
350
|
+
// Auto-initialize if needed
|
|
351
|
+
initIfNeeded();
|
|
352
|
+
|
|
353
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
354
|
+
|
|
355
|
+
if (!isInteractive) {
|
|
356
|
+
console.error('Error: Interactive mode required for adding configurations');
|
|
357
|
+
console.error('This command must be run in an interactive terminal');
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let rl = null;
|
|
362
|
+
|
|
363
|
+
const askQuestion = (question, defaultValue = '') => {
|
|
364
|
+
if (!rl) {
|
|
365
|
+
rl = readline.createInterface(
|
|
366
|
+
{input: process.stdin, output: process.stdout});
|
|
367
|
+
}
|
|
368
|
+
return new Promise(resolve => {
|
|
369
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
370
|
+
rl.question(`${question}${suffix}: `, answer => {
|
|
371
|
+
const trimmed = answer.trim();
|
|
372
|
+
resolve(trimmed ? trimmed : defaultValue.trim());
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
let baseUrl, authToken, apiKey, description;
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
if (!name) {
|
|
381
|
+
name = await askQuestion('Please enter configuration name (e.g., work)');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!name) {
|
|
385
|
+
console.error('Error: Configuration name cannot be empty');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
baseUrl = await askQuestion(
|
|
390
|
+
'Please enter ANTHROPIC_BASE_URL (can be empty, default https://api.anthropic.com)',
|
|
391
|
+
'https://api.anthropic.com');
|
|
392
|
+
|
|
393
|
+
authToken =
|
|
394
|
+
await askQuestion('Please enter ANTHROPIC_AUTH_TOKEN (can be empty)');
|
|
395
|
+
|
|
396
|
+
apiKey = await askQuestion('Please enter ANTHROPIC_API_KEY (can be empty)');
|
|
397
|
+
|
|
398
|
+
description = await askQuestion(
|
|
399
|
+
'Please enter configuration description (can be empty)');
|
|
400
|
+
} finally {
|
|
401
|
+
if (rl) {
|
|
402
|
+
rl.close();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const profiles = loadProfiles() || {profiles: {}};
|
|
407
|
+
|
|
408
|
+
if (profiles.profiles[name]) {
|
|
409
|
+
console.error(`Error: Configuration '${name}' already exists`);
|
|
410
|
+
console.error('To update, please edit the configuration file directly');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const envVars = {
|
|
415
|
+
ANTHROPIC_BASE_URL: baseUrl || '',
|
|
416
|
+
ANTHROPIC_AUTH_TOKEN: authToken || '',
|
|
417
|
+
ANTHROPIC_API_KEY: apiKey || ''
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
profiles.profiles[name] = {env: envVars, description};
|
|
421
|
+
|
|
422
|
+
saveProfiles(profiles);
|
|
423
|
+
console.log(`✓ Configuration '${name}' added`);
|
|
424
|
+
console.log('');
|
|
425
|
+
console.log('Run the following command to activate:');
|
|
426
|
+
console.log(` ccconfig use ${name}`);
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log('Saved environment variables:');
|
|
429
|
+
const safePrint = (key, value, mask = true) => {
|
|
430
|
+
if (!value) {
|
|
431
|
+
console.log(` ${key}: (not set)`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (!mask) {
|
|
435
|
+
console.log(` ${key}: ${value}`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const masked = value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
439
|
+
console.log(` ${key}: ${masked}`);
|
|
440
|
+
};
|
|
441
|
+
safePrint('ANTHROPIC_BASE_URL', envVars.ANTHROPIC_BASE_URL, false);
|
|
442
|
+
safePrint('ANTHROPIC_AUTH_TOKEN', envVars.ANTHROPIC_AUTH_TOKEN);
|
|
443
|
+
safePrint('ANTHROPIC_API_KEY', envVars.ANTHROPIC_API_KEY);
|
|
444
|
+
console.log('');
|
|
445
|
+
console.log('This information has been saved to:');
|
|
446
|
+
console.log(` ${PROFILES_FILE}`);
|
|
447
|
+
console.log(
|
|
448
|
+
'You can edit this file directly to further customize the profile:');
|
|
449
|
+
console.log(` vim ${PROFILES_FILE}`);
|
|
450
|
+
console.log('Or run ccconfig edit to open it with your preferred editor');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Remove configuration
|
|
455
|
+
*/
|
|
456
|
+
function remove(name) {
|
|
457
|
+
if (!name) {
|
|
458
|
+
console.error('Error: Missing configuration name');
|
|
459
|
+
console.error('Usage: ccconfig remove <name>');
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const profiles = loadProfiles();
|
|
464
|
+
|
|
465
|
+
if (!profiles) {
|
|
466
|
+
console.error('Error: Configuration file does not exist');
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!profiles.profiles[name]) {
|
|
471
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
delete profiles.profiles[name];
|
|
476
|
+
saveProfiles(profiles);
|
|
477
|
+
console.log(`✓ Configuration '${name}' removed`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Detect current shell and return recommended activation command
|
|
482
|
+
*/
|
|
483
|
+
function detectShellCommand() {
|
|
484
|
+
const shellPath = (process.env.SHELL || '').toLowerCase();
|
|
485
|
+
|
|
486
|
+
if (process.env.FISH_VERSION || shellPath.includes('fish')) {
|
|
487
|
+
return {shell: 'fish', command: 'ccconfig env fish | source'};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
491
|
+
shellPath.includes('zsh')) {
|
|
492
|
+
return {shell: 'zsh', command: 'eval $(ccconfig env bash)'};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
|
|
496
|
+
shellPath.includes('pwsh') || shellPath.includes('powershell')) {
|
|
497
|
+
return {shell: 'PowerShell', command: 'ccconfig env pwsh | iex'};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (shellPath.includes('bash')) {
|
|
501
|
+
return {shell: 'bash', command: 'eval $(ccconfig env bash)'};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (process.platform === 'win32') {
|
|
505
|
+
const comSpec = (process.env.ComSpec || '').toLowerCase();
|
|
506
|
+
if (comSpec.includes('powershell')) {
|
|
507
|
+
return {shell: 'PowerShell', command: 'ccconfig env pwsh | iex'};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {shell: null, command: null};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Switch configuration
|
|
516
|
+
*/
|
|
517
|
+
function use(name) {
|
|
518
|
+
const profiles = loadProfiles();
|
|
519
|
+
|
|
520
|
+
if (!profiles || !profiles.profiles ||
|
|
521
|
+
Object.keys(profiles.profiles).length === 0) {
|
|
522
|
+
console.error('Error: No configurations found');
|
|
523
|
+
console.error('Please add a configuration first: ccconfig add <name>');
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!profiles.profiles[name]) {
|
|
528
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
529
|
+
console.error('Run ccconfig list to see available configurations');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const profile = profiles.profiles[name];
|
|
534
|
+
|
|
535
|
+
if (!profile.env || Object.keys(profile.env).length === 0) {
|
|
536
|
+
console.error(
|
|
537
|
+
`Error: Configuration '${name}' has empty environment variables`);
|
|
538
|
+
console.error('Please edit the configuration file to add env field');
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const mode = getMode();
|
|
543
|
+
|
|
544
|
+
if (mode === MODE_SETTINGS) {
|
|
545
|
+
// Settings mode: directly modify ~/.claude/settings.json
|
|
546
|
+
updateClaudeSettings(profile.env);
|
|
547
|
+
|
|
548
|
+
console.log(`✓ Switched to configuration: ${name} (settings mode)`);
|
|
549
|
+
console.log(` Environment variables:`);
|
|
550
|
+
for (const [key, value] of Object.entries(profile.env)) {
|
|
551
|
+
const displayValue =
|
|
552
|
+
value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
553
|
+
console.log(` ${key}: ${displayValue}`);
|
|
554
|
+
}
|
|
555
|
+
console.log('');
|
|
556
|
+
console.log('Configuration written to ~/.claude/settings.json');
|
|
557
|
+
console.log('Restart Claude Code to make configuration take effect');
|
|
558
|
+
} else {
|
|
559
|
+
// Env mode: write to environment variable file
|
|
560
|
+
writeEnvFile(profile.env);
|
|
561
|
+
|
|
562
|
+
console.log(`✓ Switched to configuration: ${name} (env mode)`);
|
|
563
|
+
console.log(` Environment variables:`);
|
|
564
|
+
for (const [key, value] of Object.entries(profile.env)) {
|
|
565
|
+
const displayValue =
|
|
566
|
+
value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
567
|
+
console.log(` ${key}: ${displayValue}`);
|
|
568
|
+
}
|
|
569
|
+
console.log('');
|
|
570
|
+
console.log(`Environment variable file updated: ${ENV_FILE}`);
|
|
571
|
+
console.log('');
|
|
572
|
+
const shellSuggestion = detectShellCommand();
|
|
573
|
+
const applyCommands = [
|
|
574
|
+
{command: 'eval $(ccconfig env bash)', note: '# Bash/Zsh'},
|
|
575
|
+
{command: 'ccconfig env fish | source', note: '# Fish'},
|
|
576
|
+
{command: 'ccconfig env pwsh | iex', note: '# PowerShell'}
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
console.log('Apply immediately in current Shell (optional):');
|
|
580
|
+
|
|
581
|
+
if (shellSuggestion.command) {
|
|
582
|
+
console.log(
|
|
583
|
+
` ${shellSuggestion.command} # Detected ${shellSuggestion.shell}`);
|
|
584
|
+
|
|
585
|
+
const normalizedSuggestion =
|
|
586
|
+
shellSuggestion.command.replace(/\s+/g, ' ').trim();
|
|
587
|
+
for (const item of applyCommands) {
|
|
588
|
+
const normalizedCommand = item.command.replace(/\s+/g, ' ').trim();
|
|
589
|
+
if (normalizedCommand === normalizedSuggestion) {
|
|
590
|
+
item.skip = true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
for (const item of applyCommands) {
|
|
596
|
+
if (item.skip) continue;
|
|
597
|
+
console.log(` ${item.command} ${item.note}`);
|
|
598
|
+
}
|
|
599
|
+
console.log('');
|
|
600
|
+
console.log('Or restart Shell to auto-load');
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Display current configuration
|
|
606
|
+
*/
|
|
607
|
+
function current(showSecret = false) {
|
|
608
|
+
const currentMode = getMode();
|
|
609
|
+
const settings = loadClaudeSettings();
|
|
610
|
+
const envFile = readEnvFile();
|
|
611
|
+
const processEnv = {
|
|
612
|
+
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL,
|
|
613
|
+
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
|
|
614
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY
|
|
615
|
+
};
|
|
616
|
+
const currentProfile = getCurrentProfile();
|
|
617
|
+
|
|
618
|
+
console.log('═══════════════════════════════════════════');
|
|
619
|
+
console.log('Claude Code Configuration Status');
|
|
620
|
+
console.log('═══════════════════════════════════════════');
|
|
621
|
+
console.log('');
|
|
622
|
+
|
|
623
|
+
// Display current mode
|
|
624
|
+
console.log(`Current Mode: ${currentMode}`);
|
|
625
|
+
if (currentProfile) {
|
|
626
|
+
console.log(`Active Configuration: ${currentProfile}`);
|
|
627
|
+
} else {
|
|
628
|
+
console.log('Active Configuration: (no matching configuration)');
|
|
629
|
+
}
|
|
630
|
+
console.log('');
|
|
631
|
+
|
|
632
|
+
// Display settings.json configuration
|
|
633
|
+
console.log('【1】~/.claude/settings.json:');
|
|
634
|
+
if (settings.env &&
|
|
635
|
+
(settings.env.ANTHROPIC_BASE_URL || settings.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
636
|
+
const baseUrl = settings.env.ANTHROPIC_BASE_URL || '(not set)';
|
|
637
|
+
const authToken = settings.env.ANTHROPIC_AUTH_TOKEN || '(not set)';
|
|
638
|
+
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
639
|
+
authToken :
|
|
640
|
+
authToken.substring(0, 20) + '...';
|
|
641
|
+
|
|
642
|
+
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
643
|
+
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
644
|
+
} else {
|
|
645
|
+
console.log(' (not configured)');
|
|
646
|
+
}
|
|
647
|
+
console.log('');
|
|
648
|
+
|
|
649
|
+
// Display environment variable file configuration
|
|
650
|
+
console.log(`【2】Environment Variables File (${ENV_FILE}):`);
|
|
651
|
+
if (envFile &&
|
|
652
|
+
(envFile.ANTHROPIC_BASE_URL || envFile.ANTHROPIC_AUTH_TOKEN ||
|
|
653
|
+
envFile.ANTHROPIC_API_KEY)) {
|
|
654
|
+
const baseUrl = envFile.ANTHROPIC_BASE_URL || '(not set)';
|
|
655
|
+
const authToken = envFile.ANTHROPIC_AUTH_TOKEN ||
|
|
656
|
+
envFile.ANTHROPIC_API_KEY || '(not set)';
|
|
657
|
+
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
658
|
+
authToken :
|
|
659
|
+
authToken.substring(0, 20) + '...';
|
|
660
|
+
|
|
661
|
+
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
662
|
+
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
663
|
+
} else {
|
|
664
|
+
console.log(' (not configured)');
|
|
665
|
+
}
|
|
666
|
+
console.log('');
|
|
667
|
+
|
|
668
|
+
// Display current process environment variables
|
|
669
|
+
console.log('【3】Current Process Environment Variables:');
|
|
670
|
+
if (processEnv.ANTHROPIC_BASE_URL || processEnv.ANTHROPIC_AUTH_TOKEN ||
|
|
671
|
+
processEnv.ANTHROPIC_API_KEY) {
|
|
672
|
+
const baseUrl = processEnv.ANTHROPIC_BASE_URL || '(not set)';
|
|
673
|
+
const authToken = processEnv.ANTHROPIC_AUTH_TOKEN ||
|
|
674
|
+
processEnv.ANTHROPIC_API_KEY || '(not set)';
|
|
675
|
+
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
676
|
+
authToken :
|
|
677
|
+
authToken.substring(0, 20) + '...';
|
|
678
|
+
|
|
679
|
+
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
680
|
+
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
681
|
+
} else {
|
|
682
|
+
console.log(' (not set)');
|
|
683
|
+
}
|
|
684
|
+
console.log('');
|
|
685
|
+
|
|
686
|
+
// Display notes
|
|
687
|
+
console.log('───────────────────────────────────────────');
|
|
688
|
+
console.log('Notes:');
|
|
689
|
+
console.log(' • Settings mode: Claude Code reads from 【1】');
|
|
690
|
+
console.log(' • ENV mode: Claude Code reads from 【3】(loaded from 【2】)');
|
|
691
|
+
if (!showSecret) {
|
|
692
|
+
console.log('');
|
|
693
|
+
console.log('Use --show-secret to display full token');
|
|
694
|
+
}
|
|
695
|
+
console.log('═══════════════════════════════════════════');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Show configuration file path
|
|
700
|
+
*/
|
|
701
|
+
function edit() {
|
|
702
|
+
if (!fs.existsSync(PROFILES_FILE)) {
|
|
703
|
+
console.error('Error: Configuration file does not exist');
|
|
704
|
+
console.error('Please add a configuration first: ccconfig add <name>');
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vim';
|
|
709
|
+
|
|
710
|
+
console.log('Configuration file path:');
|
|
711
|
+
console.log(` ${PROFILES_FILE}`);
|
|
712
|
+
console.log('');
|
|
713
|
+
console.log('Open it with your preferred editor, for example:');
|
|
714
|
+
console.log(` ${editor} ${PROFILES_FILE}`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Switch/view mode
|
|
719
|
+
*/
|
|
720
|
+
function mode(newMode) {
|
|
721
|
+
if (!newMode) {
|
|
722
|
+
// Display current mode
|
|
723
|
+
const currentMode = getMode();
|
|
724
|
+
console.log(`Current mode: ${currentMode}`);
|
|
725
|
+
console.log('');
|
|
726
|
+
if (currentMode === MODE_SETTINGS) {
|
|
727
|
+
console.log('SETTINGS mode:');
|
|
728
|
+
console.log(' - Directly modify ~/.claude/settings.json');
|
|
729
|
+
console.log(' - No Shell configuration needed');
|
|
730
|
+
console.log(' - Restart Claude Code to take effect');
|
|
731
|
+
} else {
|
|
732
|
+
console.log('ENV mode:');
|
|
733
|
+
console.log(' - Use environment variable files');
|
|
734
|
+
console.log(' - Need to configure Shell loading script');
|
|
735
|
+
console.log(' - Cross-Shell configuration sharing');
|
|
736
|
+
}
|
|
737
|
+
console.log('');
|
|
738
|
+
console.log('Switch modes:');
|
|
739
|
+
console.log(' ccconfig mode settings');
|
|
740
|
+
console.log(' ccconfig mode env');
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (newMode !== MODE_SETTINGS && newMode !== MODE_ENV) {
|
|
745
|
+
console.error(`Error: Invalid mode '${newMode}'`);
|
|
746
|
+
console.error(`Available modes: ${MODE_SETTINGS}, ${MODE_ENV}`);
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const oldMode = getMode();
|
|
751
|
+
setMode(newMode);
|
|
752
|
+
|
|
753
|
+
console.log(`✓ Mode switched: ${oldMode} -> ${newMode}`);
|
|
754
|
+
console.log('');
|
|
755
|
+
|
|
756
|
+
if (newMode === MODE_SETTINGS) {
|
|
757
|
+
console.log('SETTINGS mode enabled');
|
|
758
|
+
console.log(
|
|
759
|
+
' Next use command will directly modify ~/.claude/settings.json');
|
|
760
|
+
} else {
|
|
761
|
+
console.log('ENV mode enabled');
|
|
762
|
+
console.log(' Next use command will write to environment variable file');
|
|
763
|
+
console.log(' Please ensure Shell loading script is configured');
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Output environment variables (for source)
|
|
769
|
+
*/
|
|
770
|
+
function env(format = 'bash') {
|
|
771
|
+
const envVars = getActiveEnvVars();
|
|
772
|
+
|
|
773
|
+
if (!envVars || Object.keys(envVars).length === 0) {
|
|
774
|
+
console.error(
|
|
775
|
+
'Error: No available environment variable configuration found');
|
|
776
|
+
console.error(
|
|
777
|
+
'Please run ccconfig use <name> to select a configuration first');
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Output all environment variables
|
|
782
|
+
switch (format) {
|
|
783
|
+
case 'fish':
|
|
784
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
785
|
+
const renderedValue = value == null ? '' : String(value);
|
|
786
|
+
console.log(`set -gx ${key} "${renderedValue}"`);
|
|
787
|
+
}
|
|
788
|
+
break;
|
|
789
|
+
case 'bash':
|
|
790
|
+
case 'zsh':
|
|
791
|
+
case 'sh':
|
|
792
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
793
|
+
const renderedValue = value == null ? '' : String(value);
|
|
794
|
+
console.log(`export ${key}="${renderedValue}"`);
|
|
795
|
+
}
|
|
796
|
+
break;
|
|
797
|
+
case 'powershell':
|
|
798
|
+
case 'pwsh':
|
|
799
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
800
|
+
const renderedValue = value == null ? '' : String(value);
|
|
801
|
+
console.log(`$env:${key}="${renderedValue}"`);
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
case 'dotenv':
|
|
805
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
806
|
+
const renderedValue = value == null ? '' : String(value);
|
|
807
|
+
console.log(`${key}=${renderedValue}`);
|
|
808
|
+
}
|
|
809
|
+
break;
|
|
810
|
+
default:
|
|
811
|
+
console.error(`Error: Unsupported format: ${format}`);
|
|
812
|
+
console.error(
|
|
813
|
+
'Supported formats: fish, bash, zsh, sh, powershell, pwsh, dotenv');
|
|
814
|
+
process.exit(1);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Display help information
|
|
820
|
+
*/
|
|
821
|
+
function help() {
|
|
822
|
+
console.log('Claude Code Configuration Manager');
|
|
823
|
+
console.log('');
|
|
824
|
+
console.log(`Profiles are stored in: ${PROFILES_FILE}`);
|
|
825
|
+
console.log('');
|
|
826
|
+
console.log('Supports two modes:');
|
|
827
|
+
console.log(
|
|
828
|
+
' env - Use environment variable files (default, cross-Shell, instant apply)');
|
|
829
|
+
console.log(
|
|
830
|
+
' settings - Directly modify ~/.claude/settings.json (no Shell config needed)');
|
|
831
|
+
console.log('');
|
|
832
|
+
console.log('Usage:');
|
|
833
|
+
console.log(' ccconfig [command] [options]');
|
|
834
|
+
console.log('');
|
|
835
|
+
console.log('Global Options:');
|
|
836
|
+
console.log(
|
|
837
|
+
' --help, -h Display this help information');
|
|
838
|
+
console.log(
|
|
839
|
+
' --version, -V Display version information');
|
|
840
|
+
console.log('');
|
|
841
|
+
console.log('Commands:');
|
|
842
|
+
console.log(
|
|
843
|
+
' list|ls List all configurations (default)');
|
|
844
|
+
console.log(
|
|
845
|
+
' add [name] Add new configuration (interactive)');
|
|
846
|
+
console.log(
|
|
847
|
+
' use <name> Switch to specified configuration');
|
|
848
|
+
console.log(
|
|
849
|
+
' remove|rm <name> Remove configuration');
|
|
850
|
+
console.log(
|
|
851
|
+
' current [--show-secret] Display current configuration');
|
|
852
|
+
console.log(
|
|
853
|
+
' mode [settings|env] View or switch mode');
|
|
854
|
+
console.log(
|
|
855
|
+
' env [format] Output environment variables (env mode)');
|
|
856
|
+
console.log(
|
|
857
|
+
' edit Show configuration file location');
|
|
858
|
+
console.log('');
|
|
859
|
+
console.log('Configuration file locations:');
|
|
860
|
+
console.log(` Configuration list: ${PROFILES_FILE}`);
|
|
861
|
+
console.log(` Claude settings: ${CLAUDE_SETTINGS}`);
|
|
862
|
+
console.log(` Environment variables file: ${ENV_FILE}`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Main program
|
|
866
|
+
async function main() {
|
|
867
|
+
const args = process.argv.slice(2);
|
|
868
|
+
|
|
869
|
+
// Handle global flags first (standardized behavior)
|
|
870
|
+
if (args.includes('--version') || args.includes('-V')) {
|
|
871
|
+
showVersion();
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
876
|
+
help();
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Extract flags
|
|
881
|
+
const showSecret = args.includes('--show-secret');
|
|
882
|
+
const filteredArgs = args.filter(
|
|
883
|
+
arg => arg !== '--show-secret' && arg !== '--version' && arg !== '-V' &&
|
|
884
|
+
arg !== '--help' && arg !== '-h');
|
|
885
|
+
|
|
886
|
+
const command = filteredArgs[0];
|
|
887
|
+
|
|
888
|
+
switch (command) {
|
|
889
|
+
case 'list':
|
|
890
|
+
case 'ls':
|
|
891
|
+
list();
|
|
892
|
+
break;
|
|
893
|
+
case 'use':
|
|
894
|
+
if (!filteredArgs[1]) {
|
|
895
|
+
console.error('Error: Missing configuration name');
|
|
896
|
+
console.error('Usage: ccconfig use <name>');
|
|
897
|
+
process.exit(1);
|
|
898
|
+
}
|
|
899
|
+
use(filteredArgs[1]);
|
|
900
|
+
break;
|
|
901
|
+
case 'add':
|
|
902
|
+
await add(filteredArgs[1]);
|
|
903
|
+
break;
|
|
904
|
+
case 'remove':
|
|
905
|
+
case 'rm':
|
|
906
|
+
remove(filteredArgs[1]);
|
|
907
|
+
break;
|
|
908
|
+
case 'current':
|
|
909
|
+
current(showSecret);
|
|
910
|
+
break;
|
|
911
|
+
case 'mode':
|
|
912
|
+
mode(filteredArgs[1]);
|
|
913
|
+
break;
|
|
914
|
+
case 'env':
|
|
915
|
+
env(filteredArgs[1] || 'bash');
|
|
916
|
+
break;
|
|
917
|
+
case 'edit':
|
|
918
|
+
edit();
|
|
919
|
+
break;
|
|
920
|
+
default:
|
|
921
|
+
if (!command) {
|
|
922
|
+
list();
|
|
923
|
+
} else {
|
|
924
|
+
console.error(`Error: Unknown command '${command}'`);
|
|
925
|
+
console.error('Run ccconfig --help to see help');
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function showVersion() {
|
|
932
|
+
console.log(`ccconfig version ${PACKAGE_VERSION}`);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
(async () => {
|
|
936
|
+
try {
|
|
937
|
+
await main();
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error(`Error: ${error.message}`);
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccconfig",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cross-platform Claude Code configuration switching tool",
|
|
5
|
+
"main": "ccconfig.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccconfig": "ccconfig.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node ccconfig.js --version"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"cli",
|
|
15
|
+
"config",
|
|
16
|
+
"environment"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|