ccconfig 1.1.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.
Files changed (3) hide show
  1. package/README.md +128 -179
  2. package/ccconfig.js +310 -41
  3. package/package.json +1 -1
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. Restart Shell or apply immediately
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 configure
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,9 +81,52 @@ 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
- Configure once by adding to your Shell startup files:
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
@@ -68,8 +134,10 @@ Configure once by adding to your Shell startup files:
68
134
  set -l ccconfig_env ~/.config/ccconfig/current.env
69
135
  if test -f $ccconfig_env
70
136
  for line in (cat $ccconfig_env)
71
- set -l parts (string split '=' $line)
72
- set -gx $parts[1] $parts[2]
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
  ```
@@ -78,7 +146,9 @@ end
78
146
  ```bash
79
147
  # Load Claude Code environment variables
80
148
  if [ -f ~/.config/ccconfig/current.env ]; then
81
- export $(grep -v '^#' ~/.config/ccconfig/current.env | xargs)
149
+ set -a
150
+ . ~/.config/ccconfig/current.env
151
+ set +a
82
152
  fi
83
153
  ```
84
154
 
@@ -86,7 +156,9 @@ fi
86
156
  ```zsh
87
157
  # Load Claude Code environment variables
88
158
  if [ -f ~/.config/ccconfig/current.env ]; then
89
- export $(grep -v '^#' ~/.config/ccconfig/current.env | xargs)
159
+ set -a
160
+ . ~/.config/ccconfig/current.env
161
+ set +a
90
162
  fi
91
163
  ```
92
164
 
@@ -103,95 +175,7 @@ if (Test-Path $cconfigEnv) {
103
175
  }
104
176
  ```
105
177
 
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/ccconfig/profiles.json`
165
- - **Claude Settings**: `~/.claude/settings.json`
166
- - **Environment Variables File**: `~/.config/ccconfig/current.env`
167
- - **Mode Settings**: `~/.config/ccconfig/mode`
168
-
169
- ## Configuration Example
170
-
171
- `~/.config/ccconfig/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
 
@@ -246,15 +230,56 @@ git commit -m "ccconfig profiles"
246
230
  ### Configuration Not Taking Effect
247
231
 
248
232
  **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`
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: `cat ~/.config/ccconfig/current.env`
255
- 2. Confirm Shell configuration is correct: `cat ~/.bashrc | grep ccconfig`
256
- 3. Restart Shell or use `eval $(ccconfig env bash)`
257
- 4. Check process environment variables: `ccconfig current`
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
 
@@ -287,82 +312,6 @@ chmod 600 ~/.config/ccconfig/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\ccconfig\`
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/ccconfig/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
@@ -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 (shellSuggestion.command) {
808
+ if (permanent) {
809
+ console.log('');
582
810
  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;
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
- for (const item of applyCommands) {
596
- if (item.skip) continue;
597
- console.log(` ${item.command} ${item.note}`);
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 --show-secret to display full token');
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
- const renderedValue = value == null ? '' : String(value);
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
- const renderedValue = value == null ? '' : String(value);
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
- const renderedValue = value == null ? '' : String(value);
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, -h Display this help information');
1096
+ ' -h, --help Display this help information');
838
1097
  console.log(
839
- ' --version, -V Display version information');
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> Switch to specified configuration');
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 [--show-secret] Display current configuration');
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 !== '--version' && arg !== '-V' &&
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccconfig",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Cross-platform Claude Code configuration switching tool",
5
5
  "main": "ccconfig.js",
6
6
  "bin": {