ccconfig 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -193
- package/ccconfig.js +311 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@ ccconfig use company
|
|
|
9
9
|
|
|
10
10
|
# Switch back to personal configuration after work
|
|
11
11
|
ccconfig use personal
|
|
12
|
+
|
|
13
|
+
# Permanently write to shell config (no need to eval or source each time)
|
|
14
|
+
ccconfig use personal --permanent # or use -p for short
|
|
12
15
|
```
|
|
13
16
|
|
|
14
17
|
## Quick Start
|
|
@@ -37,19 +40,39 @@ ccconfig add
|
|
|
37
40
|
# 3. Switch configuration
|
|
38
41
|
ccconfig use work
|
|
39
42
|
|
|
40
|
-
# 4.
|
|
43
|
+
# 4. Apply immediately (choose one method):
|
|
44
|
+
# Method A: Temporary (only in current shell)
|
|
41
45
|
eval $(ccconfig env bash) # or use the detected command from output
|
|
46
|
+
|
|
47
|
+
# Method B: Permanent (write to shell config file)
|
|
48
|
+
ccconfig use work --permanent # or -p for short
|
|
49
|
+
# Automatically detects and modifies ~/.bashrc, ~/.zshrc, or config.fish
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
### Settings Mode
|
|
45
53
|
|
|
54
|
+
Settings Mode directly modifies `~/.claude/settings.json` file, which is Claude Code's native configuration file. This mode is suitable when you don't want to configure shell scripts.
|
|
55
|
+
|
|
56
|
+
**How it works:**
|
|
57
|
+
- Writes environment variables directly into `~/.claude/settings.json` under the `env` field
|
|
58
|
+
- Claude Code reads these settings on startup
|
|
59
|
+
- No shell configuration required
|
|
60
|
+
- Requires Claude Code restart after each switch
|
|
61
|
+
|
|
62
|
+
**Setup:**
|
|
63
|
+
|
|
46
64
|
```bash
|
|
47
65
|
# 1. Switch to settings mode
|
|
48
66
|
ccconfig mode settings
|
|
49
67
|
|
|
50
68
|
# 2. Add configuration (interactive mode)
|
|
51
69
|
ccconfig add
|
|
52
|
-
# Follow the prompts to
|
|
70
|
+
# Follow the prompts to enter:
|
|
71
|
+
# - Name
|
|
72
|
+
# - ANTHROPIC_BASE_URL
|
|
73
|
+
# - ANTHROPIC_AUTH_TOKEN
|
|
74
|
+
# - ANTHROPIC_API_KEY
|
|
75
|
+
# - Description
|
|
53
76
|
|
|
54
77
|
# 3. Switch configuration
|
|
55
78
|
ccconfig use work
|
|
@@ -58,18 +81,63 @@ ccconfig use work
|
|
|
58
81
|
# Configuration is now active!
|
|
59
82
|
```
|
|
60
83
|
|
|
84
|
+
**Verification:**
|
|
85
|
+
```bash
|
|
86
|
+
# Check current configuration
|
|
87
|
+
ccconfig current
|
|
88
|
+
|
|
89
|
+
# View the settings file directly
|
|
90
|
+
cat ~/.claude/settings.json
|
|
91
|
+
```
|
|
92
|
+
|
|
61
93
|
#### ENV Mode Shell Configuration
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
You have two options to configure shell environment:
|
|
96
|
+
|
|
97
|
+
**Option 1: Automatic (Recommended)**
|
|
98
|
+
|
|
99
|
+
Use the `-p/--permanent` flag to automatically write to your shell config:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Automatically detects your shell and writes to the appropriate config file
|
|
103
|
+
ccconfig use <profile> --permanent
|
|
104
|
+
|
|
105
|
+
# You will be prompted with:
|
|
106
|
+
# - Warning about modifying shell config
|
|
107
|
+
# - Target file path
|
|
108
|
+
# - Content preview
|
|
109
|
+
# - Confirmation prompt (yes/no)
|
|
110
|
+
|
|
111
|
+
# This will modify:
|
|
112
|
+
# - Fish: ~/.config/fish/config.fish
|
|
113
|
+
# - Bash: ~/.bashrc
|
|
114
|
+
# - Zsh: ~/.zshrc
|
|
115
|
+
# - PowerShell: ~/.config/powershell/profile.ps1
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The tool will add a marked block between `# >>> ccconfig >>>` and `# <<< ccconfig <<<` markers, making it easy to identify and update later.
|
|
119
|
+
|
|
120
|
+
**Safety Features:**
|
|
121
|
+
- **User confirmation required**: You will be prompted before any file is modified
|
|
122
|
+
- **Content preview**: Shows exactly what will be written
|
|
123
|
+
- **Clear explanation**: Explains what changes will be made
|
|
124
|
+
- **Non-destructive**: Existing content is preserved, only the ccconfig block is updated
|
|
125
|
+
- **Interactive only**: Requires interactive terminal to prevent accidental modifications
|
|
126
|
+
|
|
127
|
+
**Option 2: Manual Configuration**
|
|
128
|
+
|
|
129
|
+
If you prefer to manually configure, add the following to your shell startup files:
|
|
64
130
|
|
|
65
131
|
**Fish** (`~/.config/fish/config.fish`):
|
|
66
132
|
```fish
|
|
67
133
|
# Load Claude Code environment variables
|
|
68
|
-
set -l
|
|
69
|
-
if test -f $
|
|
70
|
-
for line in (cat $
|
|
71
|
-
set -l parts (string split '=' $line)
|
|
72
|
-
|
|
134
|
+
set -l ccconfig_env ~/.config/ccconfig/current.env
|
|
135
|
+
if test -f $ccconfig_env
|
|
136
|
+
for line in (cat $ccconfig_env)
|
|
137
|
+
set -l parts (string split -m1 '=' $line)
|
|
138
|
+
if test (count $parts) -eq 2
|
|
139
|
+
set -gx $parts[1] $parts[2]
|
|
140
|
+
end
|
|
73
141
|
end
|
|
74
142
|
end
|
|
75
143
|
```
|
|
@@ -77,25 +145,29 @@ end
|
|
|
77
145
|
**Bash** (`~/.bashrc`):
|
|
78
146
|
```bash
|
|
79
147
|
# Load Claude Code environment variables
|
|
80
|
-
if [ -f ~/.config/
|
|
81
|
-
|
|
148
|
+
if [ -f ~/.config/ccconfig/current.env ]; then
|
|
149
|
+
set -a
|
|
150
|
+
. ~/.config/ccconfig/current.env
|
|
151
|
+
set +a
|
|
82
152
|
fi
|
|
83
153
|
```
|
|
84
154
|
|
|
85
155
|
**Zsh** (`~/.zshrc`):
|
|
86
156
|
```zsh
|
|
87
157
|
# Load Claude Code environment variables
|
|
88
|
-
if [ -f ~/.config/
|
|
89
|
-
|
|
158
|
+
if [ -f ~/.config/ccconfig/current.env ]; then
|
|
159
|
+
set -a
|
|
160
|
+
. ~/.config/ccconfig/current.env
|
|
161
|
+
set +a
|
|
90
162
|
fi
|
|
91
163
|
```
|
|
92
164
|
|
|
93
165
|
**PowerShell** (`$PROFILE`):
|
|
94
166
|
```powershell
|
|
95
167
|
# Load Claude Code environment variables
|
|
96
|
-
$
|
|
97
|
-
if (Test-Path $
|
|
98
|
-
Get-Content $
|
|
168
|
+
$cconfigEnv = "$env:USERPROFILE\.config\ccconfig\current.env"
|
|
169
|
+
if (Test-Path $cconfigEnv) {
|
|
170
|
+
Get-Content $cconfigEnv | ForEach-Object {
|
|
99
171
|
if ($_ -match '^([^=]+)=(.*)$') {
|
|
100
172
|
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
|
|
101
173
|
}
|
|
@@ -103,95 +175,7 @@ if (Test-Path $claudeEnv) {
|
|
|
103
175
|
}
|
|
104
176
|
```
|
|
105
177
|
|
|
106
|
-
|
|
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
|
-
```
|
|
178
|
+
**Note**: Manual configuration allows you to switch profiles dynamically by changing `current.env`, while `-p/--permanent` writes the values directly into the shell config.
|
|
195
179
|
|
|
196
180
|
## Advanced Usage
|
|
197
181
|
|
|
@@ -228,17 +212,17 @@ source .env
|
|
|
228
212
|
|
|
229
213
|
```bash
|
|
230
214
|
# Backup configuration
|
|
231
|
-
cp ~/.config/
|
|
215
|
+
cp ~/.config/ccconfig/profiles.json ~/backup/ccconfig-profiles.json
|
|
232
216
|
|
|
233
217
|
# Sync to new machine
|
|
234
|
-
scp ~/backup/
|
|
218
|
+
scp ~/backup/ccconfig-profiles.json new-machine:~/.config/ccconfig/
|
|
235
219
|
|
|
236
220
|
# Or use version control (be careful with security!)
|
|
237
|
-
cd ~/.config/
|
|
221
|
+
cd ~/.config/ccconfig
|
|
238
222
|
git init
|
|
239
223
|
echo "*.env" >> .gitignore
|
|
240
224
|
git add profiles.json
|
|
241
|
-
git commit -m "
|
|
225
|
+
git commit -m "ccconfig profiles"
|
|
242
226
|
```
|
|
243
227
|
|
|
244
228
|
## Troubleshooting
|
|
@@ -246,15 +230,56 @@ git commit -m "Claude Code profiles"
|
|
|
246
230
|
### Configuration Not Taking Effect
|
|
247
231
|
|
|
248
232
|
**Settings Mode**:
|
|
249
|
-
1. Check
|
|
250
|
-
|
|
251
|
-
|
|
233
|
+
1. **Check configuration is written correctly**:
|
|
234
|
+
```bash
|
|
235
|
+
ccconfig current
|
|
236
|
+
# Look at section 【1】~/.claude/settings.json
|
|
237
|
+
```
|
|
238
|
+
2. **Verify settings.json directly**:
|
|
239
|
+
```bash
|
|
240
|
+
cat ~/.claude/settings.json | grep -A 5 '"env"'
|
|
241
|
+
```
|
|
242
|
+
3. **Confirm Claude Code has been restarted**:
|
|
243
|
+
- Completely quit Claude Code (not just close window)
|
|
244
|
+
- Restart the application
|
|
245
|
+
4. **Check the `env` field** in `~/.claude/settings.json`:
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"env": {
|
|
249
|
+
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
|
250
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-...",
|
|
251
|
+
"ANTHROPIC_API_KEY": "sk-..."
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
252
255
|
|
|
253
256
|
**ENV Mode**:
|
|
254
|
-
1. Check environment variables file
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
1. **Check environment variables file**:
|
|
258
|
+
```bash
|
|
259
|
+
cat ~/.config/ccconfig/current.env
|
|
260
|
+
```
|
|
261
|
+
2. **If using --permanent flag**:
|
|
262
|
+
- The tool will show a warning and ask for confirmation before modifying files
|
|
263
|
+
- Check your shell config file has ccconfig block:
|
|
264
|
+
```bash
|
|
265
|
+
# For bash/zsh
|
|
266
|
+
cat ~/.bashrc | grep -A 5 "ccconfig"
|
|
267
|
+
# For fish
|
|
268
|
+
cat ~/.config/fish/config.fish | grep -A 5 "ccconfig"
|
|
269
|
+
```
|
|
270
|
+
- Restart shell or run: `source ~/.bashrc` (or equivalent for your shell)
|
|
271
|
+
- Note: You can also use `-p` as a short form of `--permanent`
|
|
272
|
+
- To cancel the operation, type "no" when prompted
|
|
273
|
+
|
|
274
|
+
3. **If using manual configuration or eval command**:
|
|
275
|
+
- Confirm Shell configuration is correct: `cat ~/.bashrc | grep ccconfig`
|
|
276
|
+
- Restart Shell or use `eval $(ccconfig env bash)`
|
|
277
|
+
|
|
278
|
+
4. **Check process environment variables**:
|
|
279
|
+
```bash
|
|
280
|
+
ccconfig current
|
|
281
|
+
# Look at section 【3】Current Process Environment Variables
|
|
282
|
+
```
|
|
258
283
|
|
|
259
284
|
### Configuration Lost After Mode Switch
|
|
260
285
|
|
|
@@ -269,9 +294,9 @@ ccconfig use work # Reapply configuration
|
|
|
269
294
|
|
|
270
295
|
```bash
|
|
271
296
|
# Fix configuration file permissions
|
|
272
|
-
chmod 600 ~/.config/
|
|
297
|
+
chmod 600 ~/.config/ccconfig/profiles.json
|
|
273
298
|
chmod 600 ~/.claude/settings.json
|
|
274
|
-
chmod 600 ~/.config/
|
|
299
|
+
chmod 600 ~/.config/ccconfig/current.env
|
|
275
300
|
```
|
|
276
301
|
|
|
277
302
|
## Security Considerations
|
|
@@ -287,82 +312,6 @@ chmod 600 ~/.config/claude-code/current.env
|
|
|
287
312
|
|
|
288
313
|
4. **Version Control**: If version controlling configurations, use encryption or private repositories
|
|
289
314
|
|
|
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
315
|
## License
|
|
367
316
|
|
|
368
317
|
MIT
|
package/ccconfig.js
CHANGED
|
@@ -6,7 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
|
|
8
8
|
// Configuration file paths
|
|
9
|
-
const CONFIG_DIR = path.join(os.homedir(), '.config', '
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'ccconfig');
|
|
10
10
|
const PROFILES_FILE = path.join(CONFIG_DIR, 'profiles.json');
|
|
11
11
|
const CLAUDE_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
12
12
|
const ENV_FILE = path.join(CONFIG_DIR, 'current.env');
|
|
@@ -511,10 +511,239 @@ function detectShellCommand() {
|
|
|
511
511
|
return {shell: null, command: null};
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
+
function escapePosix(value) {
|
|
515
|
+
const str = value == null ? '' : String(value);
|
|
516
|
+
return `'${str.replace(/'/g, `'"'"'`)}'`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function escapeFish(value) {
|
|
520
|
+
const str = value == null ? '' : String(value);
|
|
521
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function escapePwsh(value) {
|
|
525
|
+
const str = value == null ? '' : String(value);
|
|
526
|
+
return `'${str.replace(/'/g, `''`)}'`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Detect shell type and config file path
|
|
531
|
+
*/
|
|
532
|
+
function detectShellConfig() {
|
|
533
|
+
const shellPath = (process.env.SHELL || '').toLowerCase();
|
|
534
|
+
const homeDir = os.homedir();
|
|
535
|
+
|
|
536
|
+
if (process.env.FISH_VERSION || shellPath.includes('fish')) {
|
|
537
|
+
const configPath = path.join(homeDir, '.config', 'fish', 'config.fish');
|
|
538
|
+
return {shell: 'fish', configPath, detected: true};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
542
|
+
shellPath.includes('zsh')) {
|
|
543
|
+
const configPath = path.join(homeDir, '.zshrc');
|
|
544
|
+
return {shell: 'zsh', configPath, detected: true};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (shellPath.includes('bash')) {
|
|
548
|
+
if (process.platform === 'darwin') {
|
|
549
|
+
const bashProfile = path.join(homeDir, '.bash_profile');
|
|
550
|
+
const bashrc = path.join(homeDir, '.bashrc');
|
|
551
|
+
const configPath = fs.existsSync(bashProfile) || !fs.existsSync(bashrc) ?
|
|
552
|
+
bashProfile :
|
|
553
|
+
bashrc;
|
|
554
|
+
return {shell: 'bash', configPath, detected: true};
|
|
555
|
+
}
|
|
556
|
+
const configPath = path.join(homeDir, '.bashrc');
|
|
557
|
+
return {shell: 'bash', configPath, detected: true};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
|
|
561
|
+
shellPath.includes('pwsh') || shellPath.includes('powershell')) {
|
|
562
|
+
// PowerShell profile path varies by OS
|
|
563
|
+
const configPath = process.platform === 'win32' ?
|
|
564
|
+
path.join(
|
|
565
|
+
process.env.USERPROFILE || homeDir, 'Documents', 'PowerShell',
|
|
566
|
+
'Microsoft.PowerShell_profile.ps1') :
|
|
567
|
+
path.join(homeDir, '.config', 'powershell', 'profile.ps1');
|
|
568
|
+
return {shell: 'powershell', configPath, detected: true};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return {shell: null, configPath: null, detected: false};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Write environment variables permanently to shell config file
|
|
576
|
+
*/
|
|
577
|
+
async function writePermanentEnv(envVars) {
|
|
578
|
+
const shellConfig = detectShellConfig();
|
|
579
|
+
|
|
580
|
+
if (!shellConfig.detected) {
|
|
581
|
+
console.error('Error: Unable to detect shell type');
|
|
582
|
+
console.error('Supported shells: bash, zsh, fish, powershell');
|
|
583
|
+
console.error(`Current SHELL: ${process.env.SHELL || '(not set)'}`);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const {shell, configPath} = shellConfig;
|
|
588
|
+
const marker = '# >>> ccconfig >>>';
|
|
589
|
+
const markerEnd = '# <<< ccconfig <<<';
|
|
590
|
+
|
|
591
|
+
// Generate environment variable lines
|
|
592
|
+
let envBlock = '';
|
|
593
|
+
switch (shell) {
|
|
594
|
+
case 'fish':
|
|
595
|
+
envBlock = `${marker}\n`;
|
|
596
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
597
|
+
envBlock += `set -gx ${key} "${escapeFish(value)}"\n`;
|
|
598
|
+
}
|
|
599
|
+
envBlock += `${markerEnd}\n`;
|
|
600
|
+
break;
|
|
601
|
+
|
|
602
|
+
case 'bash':
|
|
603
|
+
case 'zsh':
|
|
604
|
+
envBlock = `${marker}\n`;
|
|
605
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
606
|
+
envBlock += `export ${key}=${escapePosix(value)}\n`;
|
|
607
|
+
}
|
|
608
|
+
envBlock += `${markerEnd}\n`;
|
|
609
|
+
break;
|
|
610
|
+
|
|
611
|
+
case 'powershell':
|
|
612
|
+
envBlock = `${marker}\n`;
|
|
613
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
614
|
+
envBlock += `$env:${key}=${escapePwsh(value)}\n`;
|
|
615
|
+
}
|
|
616
|
+
envBlock += `${markerEnd}\n`;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Display warning and confirmation
|
|
621
|
+
console.log('');
|
|
622
|
+
console.log('⚠️ WARNING: This will modify your shell configuration file');
|
|
623
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
624
|
+
console.log('');
|
|
625
|
+
console.log(`Target file: ${configPath}`);
|
|
626
|
+
console.log('');
|
|
627
|
+
console.log('The following block will be added/updated:');
|
|
628
|
+
console.log('───────────────────────────────────────────');
|
|
629
|
+
console.log(envBlock.trim());
|
|
630
|
+
console.log('───────────────────────────────────────────');
|
|
631
|
+
console.log('');
|
|
632
|
+
console.log('What this does:');
|
|
633
|
+
console.log(' • Adds environment variables to your shell startup file');
|
|
634
|
+
console.log(' • Uses markers to identify ccconfig-managed block');
|
|
635
|
+
console.log(' • Existing ccconfig block will be replaced if present');
|
|
636
|
+
console.log(' • Other content in the file will NOT be modified');
|
|
637
|
+
console.log('');
|
|
638
|
+
console.log('After this change:');
|
|
639
|
+
console.log(
|
|
640
|
+
' • These environment variables will load automatically on shell startup');
|
|
641
|
+
console.log(' • You can switch profiles by running this command again');
|
|
642
|
+
console.log(' • To remove, manually delete the block between the markers');
|
|
643
|
+
console.log('');
|
|
644
|
+
|
|
645
|
+
// Ask for confirmation
|
|
646
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
647
|
+
|
|
648
|
+
if (!isInteractive) {
|
|
649
|
+
console.error('Error: Cannot run in non-interactive mode');
|
|
650
|
+
console.error('The --permanent flag requires user confirmation');
|
|
651
|
+
console.error('Please run this command in an interactive terminal');
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const rl =
|
|
656
|
+
readline.createInterface({input: process.stdin, output: process.stdout});
|
|
657
|
+
|
|
658
|
+
const confirmed = await new Promise(resolve => {
|
|
659
|
+
rl.question('Do you want to proceed? (yes/no): ', answer => {
|
|
660
|
+
rl.close();
|
|
661
|
+
const normalized = answer.trim().toLowerCase();
|
|
662
|
+
resolve(normalized === 'yes' || normalized === 'y');
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
if (!confirmed) {
|
|
667
|
+
console.log('');
|
|
668
|
+
console.log('Operation cancelled.');
|
|
669
|
+
console.log('');
|
|
670
|
+
console.log('Alternative: Use temporary mode without --permanent flag:');
|
|
671
|
+
console.log(' 1. Run: ccconfig use <profile>');
|
|
672
|
+
console.log(
|
|
673
|
+
' 2. Apply: eval $(ccconfig env bash) # or equivalent for your shell');
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
console.log('');
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
// Ensure config directory exists
|
|
681
|
+
ensureDir(path.dirname(configPath));
|
|
682
|
+
|
|
683
|
+
// Read existing config file
|
|
684
|
+
let content = '';
|
|
685
|
+
if (fs.existsSync(configPath)) {
|
|
686
|
+
content = fs.readFileSync(configPath, 'utf-8');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Check if ccconfig block already exists
|
|
690
|
+
const hasBlock = content.includes(marker);
|
|
691
|
+
|
|
692
|
+
// Update content
|
|
693
|
+
if (hasBlock) {
|
|
694
|
+
// Replace existing block
|
|
695
|
+
const regex = new RegExp(
|
|
696
|
+
`${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${
|
|
697
|
+
markerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
|
|
698
|
+
'g');
|
|
699
|
+
content = content.replace(regex, envBlock);
|
|
700
|
+
} else {
|
|
701
|
+
// Append new block
|
|
702
|
+
if (content && !content.endsWith('\n')) {
|
|
703
|
+
content += '\n';
|
|
704
|
+
}
|
|
705
|
+
content += '\n' + envBlock;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Write back to config file
|
|
709
|
+
fs.writeFileSync(configPath, content, 'utf-8');
|
|
710
|
+
|
|
711
|
+
console.log(`✓ Environment variables written to ${shell} config file`);
|
|
712
|
+
console.log(` Config file: ${configPath}`);
|
|
713
|
+
console.log('');
|
|
714
|
+
console.log('To apply immediately, run:');
|
|
715
|
+
let applyCommand = '';
|
|
716
|
+
switch (shell) {
|
|
717
|
+
case 'fish':
|
|
718
|
+
applyCommand = `source "${escapeFish(configPath)}"`;
|
|
719
|
+
break;
|
|
720
|
+
case 'bash':
|
|
721
|
+
case 'zsh':
|
|
722
|
+
applyCommand = `source ${escapePosix(configPath)}`;
|
|
723
|
+
break;
|
|
724
|
+
case 'powershell':
|
|
725
|
+
applyCommand = `. ${escapePwsh(configPath)}`;
|
|
726
|
+
break;
|
|
727
|
+
default:
|
|
728
|
+
applyCommand = `source ${configPath}`;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
console.log(` ${applyCommand}`);
|
|
732
|
+
console.log('');
|
|
733
|
+
console.log('Or restart your shell');
|
|
734
|
+
|
|
735
|
+
} catch (error) {
|
|
736
|
+
console.error('');
|
|
737
|
+
console.error(
|
|
738
|
+
`Error: Unable to write to shell config file: ${error.message}`);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
514
743
|
/**
|
|
515
744
|
* Switch configuration
|
|
516
745
|
*/
|
|
517
|
-
function use(name) {
|
|
746
|
+
async function use(name, options = {}) {
|
|
518
747
|
const profiles = loadProfiles();
|
|
519
748
|
|
|
520
749
|
if (!profiles || !profiles.profiles ||
|
|
@@ -540,6 +769,7 @@ function use(name) {
|
|
|
540
769
|
}
|
|
541
770
|
|
|
542
771
|
const mode = getMode();
|
|
772
|
+
const permanent = options.permanent || false;
|
|
543
773
|
|
|
544
774
|
if (mode === MODE_SETTINGS) {
|
|
545
775
|
// Settings mode: directly modify ~/.claude/settings.json
|
|
@@ -555,6 +785,12 @@ function use(name) {
|
|
|
555
785
|
console.log('');
|
|
556
786
|
console.log('Configuration written to ~/.claude/settings.json');
|
|
557
787
|
console.log('Restart Claude Code to make configuration take effect');
|
|
788
|
+
|
|
789
|
+
if (permanent) {
|
|
790
|
+
console.log('');
|
|
791
|
+
console.log(
|
|
792
|
+
'Note: --permanent flag is ignored in settings mode (settings.json is already permanent)');
|
|
793
|
+
}
|
|
558
794
|
} else {
|
|
559
795
|
// Env mode: write to environment variable file
|
|
560
796
|
writeEnvFile(profile.env);
|
|
@@ -568,36 +804,49 @@ function use(name) {
|
|
|
568
804
|
}
|
|
569
805
|
console.log('');
|
|
570
806
|
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
807
|
|
|
581
|
-
if (
|
|
808
|
+
if (permanent) {
|
|
809
|
+
console.log('');
|
|
582
810
|
console.log(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
811
|
+
'Writing environment variables permanently to shell config...');
|
|
812
|
+
console.log('');
|
|
813
|
+
await writePermanentEnv(profile.env);
|
|
814
|
+
} else {
|
|
815
|
+
console.log('');
|
|
816
|
+
const shellSuggestion = detectShellCommand();
|
|
817
|
+
const applyCommands = [
|
|
818
|
+
{command: 'eval $(ccconfig env bash)', note: '# Bash/Zsh'},
|
|
819
|
+
{command: 'ccconfig env fish | source', note: '# Fish'},
|
|
820
|
+
{command: 'ccconfig env pwsh | iex', note: '# PowerShell'}
|
|
821
|
+
];
|
|
822
|
+
|
|
823
|
+
console.log('Apply immediately in current Shell (optional):');
|
|
824
|
+
|
|
825
|
+
if (shellSuggestion.command) {
|
|
826
|
+
console.log(` ${shellSuggestion.command} # Detected ${
|
|
827
|
+
shellSuggestion.shell}`);
|
|
828
|
+
|
|
829
|
+
const normalizedSuggestion =
|
|
830
|
+
shellSuggestion.command.replace(/\s+/g, ' ').trim();
|
|
831
|
+
for (const item of applyCommands) {
|
|
832
|
+
const normalizedCommand = item.command.replace(/\s+/g, ' ').trim();
|
|
833
|
+
if (normalizedCommand === normalizedSuggestion) {
|
|
834
|
+
item.skip = true;
|
|
835
|
+
}
|
|
591
836
|
}
|
|
592
837
|
}
|
|
593
|
-
}
|
|
594
838
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
839
|
+
for (const item of applyCommands) {
|
|
840
|
+
if (item.skip) continue;
|
|
841
|
+
console.log(` ${item.command} ${item.note}`);
|
|
842
|
+
}
|
|
843
|
+
console.log('');
|
|
844
|
+
console.log('Or restart Shell to auto-load');
|
|
845
|
+
console.log('');
|
|
846
|
+
console.log(
|
|
847
|
+
'Tip: Use -p/--permanent flag to write directly to shell config:');
|
|
848
|
+
console.log(` ccconfig use ${name} --permanent`);
|
|
598
849
|
}
|
|
599
|
-
console.log('');
|
|
600
|
-
console.log('Or restart Shell to auto-load');
|
|
601
850
|
}
|
|
602
851
|
}
|
|
603
852
|
|
|
@@ -690,7 +939,7 @@ function current(showSecret = false) {
|
|
|
690
939
|
console.log(' • ENV mode: Claude Code reads from 【3】(loaded from 【2】)');
|
|
691
940
|
if (!showSecret) {
|
|
692
941
|
console.log('');
|
|
693
|
-
console.log('Use
|
|
942
|
+
console.log('Use -s/--show-secret to display full token');
|
|
694
943
|
}
|
|
695
944
|
console.log('═══════════════════════════════════════════');
|
|
696
945
|
}
|
|
@@ -726,13 +975,26 @@ function mode(newMode) {
|
|
|
726
975
|
if (currentMode === MODE_SETTINGS) {
|
|
727
976
|
console.log('SETTINGS mode:');
|
|
728
977
|
console.log(' - Directly modify ~/.claude/settings.json');
|
|
978
|
+
console.log(
|
|
979
|
+
' - Writes environment variables into settings.json env field');
|
|
729
980
|
console.log(' - No Shell configuration needed');
|
|
730
981
|
console.log(' - Restart Claude Code to take effect');
|
|
982
|
+
console.log('');
|
|
983
|
+
console.log(' How it works:');
|
|
984
|
+
console.log(' 1. Run: ccconfig use <profile>');
|
|
985
|
+
console.log(' 2. Settings written to ~/.claude/settings.json');
|
|
986
|
+
console.log(' 3. Restart Claude Code to apply changes');
|
|
731
987
|
} else {
|
|
732
|
-
console.log('ENV mode:');
|
|
988
|
+
console.log('ENV mode (default):');
|
|
733
989
|
console.log(' - Use environment variable files');
|
|
734
990
|
console.log(' - Need to configure Shell loading script');
|
|
735
991
|
console.log(' - Cross-Shell configuration sharing');
|
|
992
|
+
console.log(' - No restart needed (instant apply)');
|
|
993
|
+
console.log('');
|
|
994
|
+
console.log(' How it works:');
|
|
995
|
+
console.log(' 1. Run: ccconfig use <profile>');
|
|
996
|
+
console.log(' 2. Writes to ~/.config/ccconfig/current.env');
|
|
997
|
+
console.log(' 3. Shell loads on startup or eval command');
|
|
736
998
|
}
|
|
737
999
|
console.log('');
|
|
738
1000
|
console.log('Switch modes:');
|
|
@@ -782,23 +1044,20 @@ function env(format = 'bash') {
|
|
|
782
1044
|
switch (format) {
|
|
783
1045
|
case 'fish':
|
|
784
1046
|
for (const [key, value] of Object.entries(envVars)) {
|
|
785
|
-
|
|
786
|
-
console.log(`set -gx ${key} "${renderedValue}"`);
|
|
1047
|
+
console.log(`set -gx ${key} "${escapeFish(value)}"`);
|
|
787
1048
|
}
|
|
788
1049
|
break;
|
|
789
1050
|
case 'bash':
|
|
790
1051
|
case 'zsh':
|
|
791
1052
|
case 'sh':
|
|
792
1053
|
for (const [key, value] of Object.entries(envVars)) {
|
|
793
|
-
|
|
794
|
-
console.log(`export ${key}="${renderedValue}"`);
|
|
1054
|
+
console.log(`export ${key}=${escapePosix(value)}`);
|
|
795
1055
|
}
|
|
796
1056
|
break;
|
|
797
1057
|
case 'powershell':
|
|
798
1058
|
case 'pwsh':
|
|
799
1059
|
for (const [key, value] of Object.entries(envVars)) {
|
|
800
|
-
|
|
801
|
-
console.log(`$env:${key}="${renderedValue}"`);
|
|
1060
|
+
console.log(`$env:${key}=${escapePwsh(value)}`);
|
|
802
1061
|
}
|
|
803
1062
|
break;
|
|
804
1063
|
case 'dotenv':
|
|
@@ -834,9 +1093,9 @@ function help() {
|
|
|
834
1093
|
console.log('');
|
|
835
1094
|
console.log('Global Options:');
|
|
836
1095
|
console.log(
|
|
837
|
-
' --help
|
|
1096
|
+
' -h, --help Display this help information');
|
|
838
1097
|
console.log(
|
|
839
|
-
' --version
|
|
1098
|
+
' -V, --version Display version information');
|
|
840
1099
|
console.log('');
|
|
841
1100
|
console.log('Commands:');
|
|
842
1101
|
console.log(
|
|
@@ -844,11 +1103,11 @@ function help() {
|
|
|
844
1103
|
console.log(
|
|
845
1104
|
' add [name] Add new configuration (interactive)');
|
|
846
1105
|
console.log(
|
|
847
|
-
' use <name>
|
|
1106
|
+
' use <name> [-p|--permanent] Switch to specified configuration');
|
|
848
1107
|
console.log(
|
|
849
1108
|
' remove|rm <name> Remove configuration');
|
|
850
1109
|
console.log(
|
|
851
|
-
' current [
|
|
1110
|
+
' current [-s|--show-secret] Display current configuration');
|
|
852
1111
|
console.log(
|
|
853
1112
|
' mode [settings|env] View or switch mode');
|
|
854
1113
|
console.log(
|
|
@@ -856,6 +1115,14 @@ function help() {
|
|
|
856
1115
|
console.log(
|
|
857
1116
|
' edit Show configuration file location');
|
|
858
1117
|
console.log('');
|
|
1118
|
+
console.log('Flags:');
|
|
1119
|
+
console.log(
|
|
1120
|
+
' -p, --permanent Write environment variables permanently to shell config');
|
|
1121
|
+
console.log(
|
|
1122
|
+
' (only effective in env mode with use command)');
|
|
1123
|
+
console.log(
|
|
1124
|
+
' -s, --show-secret Show full token in current command');
|
|
1125
|
+
console.log('');
|
|
859
1126
|
console.log('Configuration file locations:');
|
|
860
1127
|
console.log(` Configuration list: ${PROFILES_FILE}`);
|
|
861
1128
|
console.log(` Claude settings: ${CLAUDE_SETTINGS}`);
|
|
@@ -878,9 +1145,11 @@ async function main() {
|
|
|
878
1145
|
}
|
|
879
1146
|
|
|
880
1147
|
// Extract flags
|
|
881
|
-
const showSecret = args.includes('--show-secret');
|
|
1148
|
+
const showSecret = args.includes('--show-secret') || args.includes('-s');
|
|
1149
|
+
const permanent = args.includes('--permanent') || args.includes('-p');
|
|
882
1150
|
const filteredArgs = args.filter(
|
|
883
|
-
arg => arg !== '--show-secret' && arg !== '
|
|
1151
|
+
arg => arg !== '--show-secret' && arg !== '-s' && arg !== '--permanent' &&
|
|
1152
|
+
arg !== '-p' && arg !== '--version' && arg !== '-V' &&
|
|
884
1153
|
arg !== '--help' && arg !== '-h');
|
|
885
1154
|
|
|
886
1155
|
const command = filteredArgs[0];
|
|
@@ -893,10 +1162,10 @@ async function main() {
|
|
|
893
1162
|
case 'use':
|
|
894
1163
|
if (!filteredArgs[1]) {
|
|
895
1164
|
console.error('Error: Missing configuration name');
|
|
896
|
-
console.error('Usage: ccconfig use <name>');
|
|
1165
|
+
console.error('Usage: ccconfig use <name> [-p|--permanent]');
|
|
897
1166
|
process.exit(1);
|
|
898
1167
|
}
|
|
899
|
-
use(filteredArgs[1]);
|
|
1168
|
+
await use(filteredArgs[1], {permanent});
|
|
900
1169
|
break;
|
|
901
1170
|
case 'add':
|
|
902
1171
|
await add(filteredArgs[1]);
|
package/package.json
CHANGED