ccconfig 1.1.0 → 1.3.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 (4) hide show
  1. package/README.md +198 -160
  2. package/README_zh.md +406 -0
  3. package/ccconfig.js +767 -59
  4. package/package.json +1 -1
package/ccconfig.js CHANGED
@@ -147,6 +147,8 @@ function updateClaudeSettings(envVars) {
147
147
  delete settings.env.ANTHROPIC_BASE_URL;
148
148
  delete settings.env.ANTHROPIC_AUTH_TOKEN;
149
149
  delete settings.env.ANTHROPIC_API_KEY;
150
+ delete settings.env.ANTHROPIC_MODEL;
151
+ delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
150
152
 
151
153
  // Set new environment variables
152
154
  Object.assign(settings.env, envVars);
@@ -323,8 +325,11 @@ function list() {
323
325
  if (profile.env && profile.env.ANTHROPIC_BASE_URL) {
324
326
  console.log(` URL: ${profile.env.ANTHROPIC_BASE_URL}`);
325
327
  }
326
- if (profile.description) {
327
- console.log(` Description: ${profile.description}`);
328
+ if (profile.env && profile.env.ANTHROPIC_MODEL) {
329
+ console.log(` Model: ${profile.env.ANTHROPIC_MODEL}`);
330
+ }
331
+ if (profile.env && profile.env.ANTHROPIC_SMALL_FAST_MODEL) {
332
+ console.log(` Small Fast Model: ${profile.env.ANTHROPIC_SMALL_FAST_MODEL}`);
328
333
  }
329
334
  console.log('');
330
335
  }
@@ -374,7 +379,8 @@ async function add(name) {
374
379
  });
375
380
  };
376
381
 
377
- let baseUrl, authToken, apiKey, description;
382
+ let baseUrl, authToken, apiKey, model, smallFastModel;
383
+ let profiles;
378
384
 
379
385
  try {
380
386
  if (!name) {
@@ -386,38 +392,48 @@ async function add(name) {
386
392
  process.exit(1);
387
393
  }
388
394
 
395
+ // Check if configuration already exists before asking for details
396
+ profiles = loadProfiles() || {profiles: {}};
397
+
398
+ if (profiles.profiles[name]) {
399
+ console.error(`Error: Configuration '${name}' already exists`);
400
+ console.error('To update, please edit the configuration file directly');
401
+ process.exit(1);
402
+ }
403
+
389
404
  baseUrl = await askQuestion(
390
- 'Please enter ANTHROPIC_BASE_URL (can be empty, default https://api.anthropic.com)',
405
+ 'Please enter ANTHROPIC_BASE_URL (press Enter for default)',
391
406
  'https://api.anthropic.com');
392
407
 
393
408
  authToken =
394
- await askQuestion('Please enter ANTHROPIC_AUTH_TOKEN (can be empty)');
409
+ await askQuestion('Please enter ANTHROPIC_AUTH_TOKEN (press Enter to set as empty)');
395
410
 
396
- apiKey = await askQuestion('Please enter ANTHROPIC_API_KEY (can be empty)');
411
+ apiKey = await askQuestion('Please enter ANTHROPIC_API_KEY (press Enter to set as empty)');
397
412
 
398
- description = await askQuestion(
399
- 'Please enter configuration description (can be empty)');
413
+ model = await askQuestion('Please enter ANTHROPIC_MODEL (press Enter to skip)');
414
+
415
+ smallFastModel = await askQuestion('Please enter ANTHROPIC_SMALL_FAST_MODEL (press Enter to skip)');
400
416
  } finally {
401
417
  if (rl) {
402
418
  rl.close();
403
419
  }
404
420
  }
405
421
 
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
422
  const envVars = {
415
423
  ANTHROPIC_BASE_URL: baseUrl || '',
416
424
  ANTHROPIC_AUTH_TOKEN: authToken || '',
417
425
  ANTHROPIC_API_KEY: apiKey || ''
418
426
  };
419
427
 
420
- profiles.profiles[name] = {env: envVars, description};
428
+ // Add optional model variables if provided
429
+ if (model) {
430
+ envVars.ANTHROPIC_MODEL = model;
431
+ }
432
+ if (smallFastModel) {
433
+ envVars.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel;
434
+ }
435
+
436
+ profiles.profiles[name] = {env: envVars};
421
437
 
422
438
  saveProfiles(profiles);
423
439
  console.log(`✓ Configuration '${name}' added`);
@@ -441,6 +457,12 @@ async function add(name) {
441
457
  safePrint('ANTHROPIC_BASE_URL', envVars.ANTHROPIC_BASE_URL, false);
442
458
  safePrint('ANTHROPIC_AUTH_TOKEN', envVars.ANTHROPIC_AUTH_TOKEN);
443
459
  safePrint('ANTHROPIC_API_KEY', envVars.ANTHROPIC_API_KEY);
460
+ if (envVars.ANTHROPIC_MODEL) {
461
+ safePrint('ANTHROPIC_MODEL', envVars.ANTHROPIC_MODEL, false);
462
+ }
463
+ if (envVars.ANTHROPIC_SMALL_FAST_MODEL) {
464
+ safePrint('ANTHROPIC_SMALL_FAST_MODEL', envVars.ANTHROPIC_SMALL_FAST_MODEL, false);
465
+ }
444
466
  console.log('');
445
467
  console.log('This information has been saved to:');
446
468
  console.log(` ${PROFILES_FILE}`);
@@ -450,6 +472,131 @@ async function add(name) {
450
472
  console.log('Or run ccconfig edit to open it with your preferred editor');
451
473
  }
452
474
 
475
+ /**
476
+ * Update existing configuration
477
+ */
478
+ async function update(name) {
479
+ // Auto-initialize if needed
480
+ initIfNeeded();
481
+
482
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
483
+
484
+ if (!isInteractive) {
485
+ console.error('Error: Interactive mode required for updating configurations');
486
+ console.error('This command must be run in an interactive terminal');
487
+ process.exit(1);
488
+ }
489
+
490
+ let rl = null;
491
+
492
+ const askQuestion = (question, defaultValue = '') => {
493
+ if (!rl) {
494
+ rl = readline.createInterface(
495
+ {input: process.stdin, output: process.stdout});
496
+ }
497
+ return new Promise(resolve => {
498
+ const suffix = defaultValue ? ` [${defaultValue}]` : '';
499
+ rl.question(`${question}${suffix}: `, answer => {
500
+ const trimmed = answer.trim();
501
+ resolve(trimmed ? trimmed : defaultValue);
502
+ });
503
+ });
504
+ };
505
+
506
+ let baseUrl, authToken, apiKey, model, smallFastModel;
507
+ let profiles;
508
+
509
+ try {
510
+ if (!name) {
511
+ name = await askQuestion('Please enter configuration name to update');
512
+ }
513
+
514
+ if (!name) {
515
+ console.error('Error: Configuration name cannot be empty');
516
+ process.exit(1);
517
+ }
518
+
519
+ // Check if configuration exists
520
+ profiles = loadProfiles() || {profiles: {}};
521
+
522
+ if (!profiles.profiles[name]) {
523
+ console.error(`Error: Configuration '${name}' does not exist`);
524
+ console.error('Run ccconfig list to see available configurations');
525
+ console.error(`Or use 'ccconfig add ${name}' to create a new configuration`);
526
+ process.exit(1);
527
+ }
528
+
529
+ const existingProfile = profiles.profiles[name];
530
+ const existingEnv = existingProfile.env || {};
531
+
532
+ console.log(`Updating configuration '${name}'`);
533
+ console.log('Press Enter to keep current value/default, or enter new value to update');
534
+ console.log('');
535
+
536
+ baseUrl = await askQuestion(
537
+ 'ANTHROPIC_BASE_URL (press Enter to keep current/default)',
538
+ existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com');
539
+
540
+ authToken =
541
+ await askQuestion('ANTHROPIC_AUTH_TOKEN (press Enter to keep current/set empty)', existingEnv.ANTHROPIC_AUTH_TOKEN || '');
542
+
543
+ apiKey = await askQuestion('ANTHROPIC_API_KEY (press Enter to keep current/set empty)', existingEnv.ANTHROPIC_API_KEY || '');
544
+
545
+ model = await askQuestion('ANTHROPIC_MODEL (press Enter to skip/keep current)', existingEnv.ANTHROPIC_MODEL || '');
546
+
547
+ smallFastModel = await askQuestion('ANTHROPIC_SMALL_FAST_MODEL (press Enter to skip/keep current)', existingEnv.ANTHROPIC_SMALL_FAST_MODEL || '');
548
+ } finally {
549
+ if (rl) {
550
+ rl.close();
551
+ }
552
+ }
553
+
554
+ const envVars = {
555
+ ANTHROPIC_BASE_URL: baseUrl || '',
556
+ ANTHROPIC_AUTH_TOKEN: authToken || '',
557
+ ANTHROPIC_API_KEY: apiKey || ''
558
+ };
559
+
560
+ // Add optional model variables if provided
561
+ if (model) {
562
+ envVars.ANTHROPIC_MODEL = model;
563
+ }
564
+ if (smallFastModel) {
565
+ envVars.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel;
566
+ }
567
+
568
+ profiles.profiles[name] = {env: envVars};
569
+
570
+ saveProfiles(profiles);
571
+ console.log(`✓ Configuration '${name}' updated`);
572
+ console.log('');
573
+ console.log('Updated environment variables:');
574
+ const safePrint = (key, value, mask = true) => {
575
+ if (!value) {
576
+ console.log(` ${key}: (not set)`);
577
+ return;
578
+ }
579
+ if (!mask) {
580
+ console.log(` ${key}: ${value}`);
581
+ return;
582
+ }
583
+ const masked = value.length > 20 ? value.substring(0, 20) + '...' : value;
584
+ console.log(` ${key}: ${masked}`);
585
+ };
586
+ safePrint('ANTHROPIC_BASE_URL', envVars.ANTHROPIC_BASE_URL, false);
587
+ safePrint('ANTHROPIC_AUTH_TOKEN', envVars.ANTHROPIC_AUTH_TOKEN);
588
+ safePrint('ANTHROPIC_API_KEY', envVars.ANTHROPIC_API_KEY);
589
+ if (envVars.ANTHROPIC_MODEL) {
590
+ safePrint('ANTHROPIC_MODEL', envVars.ANTHROPIC_MODEL, false);
591
+ }
592
+ if (envVars.ANTHROPIC_SMALL_FAST_MODEL) {
593
+ safePrint('ANTHROPIC_SMALL_FAST_MODEL', envVars.ANTHROPIC_SMALL_FAST_MODEL, false);
594
+ }
595
+ console.log('');
596
+ console.log('Run the following command to activate:');
597
+ console.log(` ccconfig use ${name}`);
598
+ }
599
+
453
600
  /**
454
601
  * Remove configuration
455
602
  */
@@ -511,10 +658,239 @@ function detectShellCommand() {
511
658
  return {shell: null, command: null};
512
659
  }
513
660
 
661
+ function escapePosix(value) {
662
+ const str = value == null ? '' : String(value);
663
+ return `'${str.replace(/'/g, `'"'"'`)}'`;
664
+ }
665
+
666
+ function escapeFish(value) {
667
+ const str = value == null ? '' : String(value);
668
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
669
+ }
670
+
671
+ function escapePwsh(value) {
672
+ const str = value == null ? '' : String(value);
673
+ return `'${str.replace(/'/g, `''`)}'`;
674
+ }
675
+
676
+ /**
677
+ * Detect shell type and config file path
678
+ */
679
+ function detectShellConfig() {
680
+ const shellPath = (process.env.SHELL || '').toLowerCase();
681
+ const homeDir = os.homedir();
682
+
683
+ if (process.env.FISH_VERSION || shellPath.includes('fish')) {
684
+ const configPath = path.join(homeDir, '.config', 'fish', 'config.fish');
685
+ return {shell: 'fish', configPath, detected: true};
686
+ }
687
+
688
+ if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
689
+ shellPath.includes('zsh')) {
690
+ const configPath = path.join(homeDir, '.zshrc');
691
+ return {shell: 'zsh', configPath, detected: true};
692
+ }
693
+
694
+ if (shellPath.includes('bash')) {
695
+ if (process.platform === 'darwin') {
696
+ const bashProfile = path.join(homeDir, '.bash_profile');
697
+ const bashrc = path.join(homeDir, '.bashrc');
698
+ const configPath = fs.existsSync(bashProfile) || !fs.existsSync(bashrc) ?
699
+ bashProfile :
700
+ bashrc;
701
+ return {shell: 'bash', configPath, detected: true};
702
+ }
703
+ const configPath = path.join(homeDir, '.bashrc');
704
+ return {shell: 'bash', configPath, detected: true};
705
+ }
706
+
707
+ if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
708
+ shellPath.includes('pwsh') || shellPath.includes('powershell')) {
709
+ // PowerShell profile path varies by OS
710
+ const configPath = process.platform === 'win32' ?
711
+ path.join(
712
+ process.env.USERPROFILE || homeDir, 'Documents', 'PowerShell',
713
+ 'Microsoft.PowerShell_profile.ps1') :
714
+ path.join(homeDir, '.config', 'powershell', 'profile.ps1');
715
+ return {shell: 'powershell', configPath, detected: true};
716
+ }
717
+
718
+ return {shell: null, configPath: null, detected: false};
719
+ }
720
+
721
+ /**
722
+ * Write environment variables permanently to shell config file
723
+ */
724
+ async function writePermanentEnv(envVars) {
725
+ const shellConfig = detectShellConfig();
726
+
727
+ if (!shellConfig.detected) {
728
+ console.error('Error: Unable to detect shell type');
729
+ console.error('Supported shells: bash, zsh, fish, powershell');
730
+ console.error(`Current SHELL: ${process.env.SHELL || '(not set)'}`);
731
+ process.exit(1);
732
+ }
733
+
734
+ const {shell, configPath} = shellConfig;
735
+ const marker = '# >>> ccconfig >>>';
736
+ const markerEnd = '# <<< ccconfig <<<';
737
+
738
+ // Generate environment variable lines
739
+ let envBlock = '';
740
+ switch (shell) {
741
+ case 'fish':
742
+ envBlock = `${marker}\n`;
743
+ for (const [key, value] of Object.entries(envVars)) {
744
+ envBlock += `set -gx ${key} "${escapeFish(value)}"\n`;
745
+ }
746
+ envBlock += `${markerEnd}\n`;
747
+ break;
748
+
749
+ case 'bash':
750
+ case 'zsh':
751
+ envBlock = `${marker}\n`;
752
+ for (const [key, value] of Object.entries(envVars)) {
753
+ envBlock += `export ${key}=${escapePosix(value)}\n`;
754
+ }
755
+ envBlock += `${markerEnd}\n`;
756
+ break;
757
+
758
+ case 'powershell':
759
+ envBlock = `${marker}\n`;
760
+ for (const [key, value] of Object.entries(envVars)) {
761
+ envBlock += `$env:${key}=${escapePwsh(value)}\n`;
762
+ }
763
+ envBlock += `${markerEnd}\n`;
764
+ break;
765
+ }
766
+
767
+ // Display warning and confirmation
768
+ console.log('');
769
+ console.log('⚠️ WARNING: This will modify your shell configuration file');
770
+ console.log('═══════════════════════════════════════════════════════════');
771
+ console.log('');
772
+ console.log(`Target file: ${configPath}`);
773
+ console.log('');
774
+ console.log('The following block will be added/updated:');
775
+ console.log('───────────────────────────────────────────');
776
+ console.log(envBlock.trim());
777
+ console.log('───────────────────────────────────────────');
778
+ console.log('');
779
+ console.log('What this does:');
780
+ console.log(' • Adds environment variables to your shell startup file');
781
+ console.log(' • Uses markers to identify ccconfig-managed block');
782
+ console.log(' • Existing ccconfig block will be replaced if present');
783
+ console.log(' • Other content in the file will NOT be modified');
784
+ console.log('');
785
+ console.log('After this change:');
786
+ console.log(
787
+ ' • These environment variables will load automatically on shell startup');
788
+ console.log(' • You can switch profiles by running this command again');
789
+ console.log(' • To remove, manually delete the block between the markers');
790
+ console.log('');
791
+
792
+ // Ask for confirmation
793
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
794
+
795
+ if (!isInteractive) {
796
+ console.error('Error: Cannot run in non-interactive mode');
797
+ console.error('The --permanent flag requires user confirmation');
798
+ console.error('Please run this command in an interactive terminal');
799
+ process.exit(1);
800
+ }
801
+
802
+ const rl =
803
+ readline.createInterface({input: process.stdin, output: process.stdout});
804
+
805
+ const confirmed = await new Promise(resolve => {
806
+ rl.question('Do you want to proceed? (yes/no): ', answer => {
807
+ rl.close();
808
+ const normalized = answer.trim().toLowerCase();
809
+ resolve(normalized === 'yes' || normalized === 'y');
810
+ });
811
+ });
812
+
813
+ if (!confirmed) {
814
+ console.log('');
815
+ console.log('Operation cancelled.');
816
+ console.log('');
817
+ console.log('Alternative: Use temporary mode without --permanent flag:');
818
+ console.log(' 1. Run: ccconfig use <profile>');
819
+ console.log(
820
+ ' 2. Apply: eval $(ccconfig env bash) # or equivalent for your shell');
821
+ return;
822
+ }
823
+
824
+ console.log('');
825
+
826
+ try {
827
+ // Ensure config directory exists
828
+ ensureDir(path.dirname(configPath));
829
+
830
+ // Read existing config file
831
+ let content = '';
832
+ if (fs.existsSync(configPath)) {
833
+ content = fs.readFileSync(configPath, 'utf-8');
834
+ }
835
+
836
+ // Check if ccconfig block already exists
837
+ const hasBlock = content.includes(marker);
838
+
839
+ // Update content
840
+ if (hasBlock) {
841
+ // Replace existing block
842
+ const regex = new RegExp(
843
+ `${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${
844
+ markerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
845
+ 'g');
846
+ content = content.replace(regex, envBlock);
847
+ } else {
848
+ // Append new block
849
+ if (content && !content.endsWith('\n')) {
850
+ content += '\n';
851
+ }
852
+ content += '\n' + envBlock;
853
+ }
854
+
855
+ // Write back to config file
856
+ fs.writeFileSync(configPath, content, 'utf-8');
857
+
858
+ console.log(`✓ Environment variables written to ${shell} config file`);
859
+ console.log(` Config file: ${configPath}`);
860
+ console.log('');
861
+ console.log('To apply immediately, run:');
862
+ let applyCommand = '';
863
+ switch (shell) {
864
+ case 'fish':
865
+ applyCommand = `source "${escapeFish(configPath)}"`;
866
+ break;
867
+ case 'bash':
868
+ case 'zsh':
869
+ applyCommand = `source ${escapePosix(configPath)}`;
870
+ break;
871
+ case 'powershell':
872
+ applyCommand = `. ${escapePwsh(configPath)}`;
873
+ break;
874
+ default:
875
+ applyCommand = `source ${configPath}`;
876
+ break;
877
+ }
878
+ console.log(` ${applyCommand}`);
879
+ console.log('');
880
+ console.log('Or restart your shell');
881
+
882
+ } catch (error) {
883
+ console.error('');
884
+ console.error(
885
+ `Error: Unable to write to shell config file: ${error.message}`);
886
+ process.exit(1);
887
+ }
888
+ }
889
+
514
890
  /**
515
891
  * Switch configuration
516
892
  */
517
- function use(name) {
893
+ async function use(name, options = {}) {
518
894
  const profiles = loadProfiles();
519
895
 
520
896
  if (!profiles || !profiles.profiles ||
@@ -540,6 +916,7 @@ function use(name) {
540
916
  }
541
917
 
542
918
  const mode = getMode();
919
+ const permanent = options.permanent || false;
543
920
 
544
921
  if (mode === MODE_SETTINGS) {
545
922
  // Settings mode: directly modify ~/.claude/settings.json
@@ -555,6 +932,12 @@ function use(name) {
555
932
  console.log('');
556
933
  console.log('Configuration written to ~/.claude/settings.json');
557
934
  console.log('Restart Claude Code to make configuration take effect');
935
+
936
+ if (permanent) {
937
+ console.log('');
938
+ console.log(
939
+ 'Note: --permanent flag is ignored in settings mode (settings.json is already permanent)');
940
+ }
558
941
  } else {
559
942
  // Env mode: write to environment variable file
560
943
  writeEnvFile(profile.env);
@@ -568,36 +951,49 @@ function use(name) {
568
951
  }
569
952
  console.log('');
570
953
  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
954
 
579
- console.log('Apply immediately in current Shell (optional):');
580
-
581
- if (shellSuggestion.command) {
955
+ if (permanent) {
956
+ console.log('');
582
957
  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;
958
+ 'Writing environment variables permanently to shell config...');
959
+ console.log('');
960
+ await writePermanentEnv(profile.env);
961
+ } else {
962
+ console.log('');
963
+ const shellSuggestion = detectShellCommand();
964
+ const applyCommands = [
965
+ {command: 'eval $(ccconfig env bash)', note: '# Bash/Zsh'},
966
+ {command: 'ccconfig env fish | source', note: '# Fish'},
967
+ {command: 'ccconfig env pwsh | iex', note: '# PowerShell'}
968
+ ];
969
+
970
+ console.log('Apply immediately in current Shell (optional):');
971
+
972
+ if (shellSuggestion.command) {
973
+ console.log(` ${shellSuggestion.command} # Detected ${
974
+ shellSuggestion.shell}`);
975
+
976
+ const normalizedSuggestion =
977
+ shellSuggestion.command.replace(/\s+/g, ' ').trim();
978
+ for (const item of applyCommands) {
979
+ const normalizedCommand = item.command.replace(/\s+/g, ' ').trim();
980
+ if (normalizedCommand === normalizedSuggestion) {
981
+ item.skip = true;
982
+ }
591
983
  }
592
984
  }
593
- }
594
985
 
595
- for (const item of applyCommands) {
596
- if (item.skip) continue;
597
- console.log(` ${item.command} ${item.note}`);
986
+ for (const item of applyCommands) {
987
+ if (item.skip) continue;
988
+ console.log(` ${item.command} ${item.note}`);
989
+ }
990
+ console.log('');
991
+ console.log('Or restart Shell to auto-load');
992
+ console.log('');
993
+ console.log(
994
+ 'Tip: Use -p/--permanent flag to write directly to shell config:');
995
+ console.log(` ccconfig use ${name} --permanent`);
598
996
  }
599
- console.log('');
600
- console.log('Or restart Shell to auto-load');
601
997
  }
602
998
  }
603
999
 
@@ -611,7 +1007,9 @@ function current(showSecret = false) {
611
1007
  const processEnv = {
612
1008
  ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL,
613
1009
  ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
614
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY
1010
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
1011
+ ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL,
1012
+ ANTHROPIC_SMALL_FAST_MODEL: process.env.ANTHROPIC_SMALL_FAST_MODEL
615
1013
  };
616
1014
  const currentProfile = getCurrentProfile();
617
1015
 
@@ -641,6 +1039,12 @@ function current(showSecret = false) {
641
1039
 
642
1040
  console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
643
1041
  console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
1042
+ if (settings.env.ANTHROPIC_MODEL) {
1043
+ console.log(` ANTHROPIC_MODEL: ${settings.env.ANTHROPIC_MODEL}`);
1044
+ }
1045
+ if (settings.env.ANTHROPIC_SMALL_FAST_MODEL) {
1046
+ console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${settings.env.ANTHROPIC_SMALL_FAST_MODEL}`);
1047
+ }
644
1048
  } else {
645
1049
  console.log(' (not configured)');
646
1050
  }
@@ -660,6 +1064,12 @@ function current(showSecret = false) {
660
1064
 
661
1065
  console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
662
1066
  console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
1067
+ if (envFile.ANTHROPIC_MODEL) {
1068
+ console.log(` ANTHROPIC_MODEL: ${envFile.ANTHROPIC_MODEL}`);
1069
+ }
1070
+ if (envFile.ANTHROPIC_SMALL_FAST_MODEL) {
1071
+ console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${envFile.ANTHROPIC_SMALL_FAST_MODEL}`);
1072
+ }
663
1073
  } else {
664
1074
  console.log(' (not configured)');
665
1075
  }
@@ -678,6 +1088,12 @@ function current(showSecret = false) {
678
1088
 
679
1089
  console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
680
1090
  console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
1091
+ if (processEnv.ANTHROPIC_MODEL) {
1092
+ console.log(` ANTHROPIC_MODEL: ${processEnv.ANTHROPIC_MODEL}`);
1093
+ }
1094
+ if (processEnv.ANTHROPIC_SMALL_FAST_MODEL) {
1095
+ console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${processEnv.ANTHROPIC_SMALL_FAST_MODEL}`);
1096
+ }
681
1097
  } else {
682
1098
  console.log(' (not set)');
683
1099
  }
@@ -690,7 +1106,7 @@ function current(showSecret = false) {
690
1106
  console.log(' • ENV mode: Claude Code reads from 【3】(loaded from 【2】)');
691
1107
  if (!showSecret) {
692
1108
  console.log('');
693
- console.log('Use --show-secret to display full token');
1109
+ console.log('Use -s/--show-secret to display full token');
694
1110
  }
695
1111
  console.log('═══════════════════════════════════════════');
696
1112
  }
@@ -726,13 +1142,26 @@ function mode(newMode) {
726
1142
  if (currentMode === MODE_SETTINGS) {
727
1143
  console.log('SETTINGS mode:');
728
1144
  console.log(' - Directly modify ~/.claude/settings.json');
1145
+ console.log(
1146
+ ' - Writes environment variables into settings.json env field');
729
1147
  console.log(' - No Shell configuration needed');
730
1148
  console.log(' - Restart Claude Code to take effect');
1149
+ console.log('');
1150
+ console.log(' How it works:');
1151
+ console.log(' 1. Run: ccconfig use <profile>');
1152
+ console.log(' 2. Settings written to ~/.claude/settings.json');
1153
+ console.log(' 3. Restart Claude Code to apply changes');
731
1154
  } else {
732
- console.log('ENV mode:');
1155
+ console.log('ENV mode (default):');
733
1156
  console.log(' - Use environment variable files');
734
1157
  console.log(' - Need to configure Shell loading script');
735
1158
  console.log(' - Cross-Shell configuration sharing');
1159
+ console.log(' - No restart needed (instant apply)');
1160
+ console.log('');
1161
+ console.log(' How it works:');
1162
+ console.log(' 1. Run: ccconfig use <profile>');
1163
+ console.log(' 2. Writes to ~/.config/ccconfig/current.env');
1164
+ console.log(' 3. Shell loads on startup or eval command');
736
1165
  }
737
1166
  console.log('');
738
1167
  console.log('Switch modes:');
@@ -782,23 +1211,20 @@ function env(format = 'bash') {
782
1211
  switch (format) {
783
1212
  case 'fish':
784
1213
  for (const [key, value] of Object.entries(envVars)) {
785
- const renderedValue = value == null ? '' : String(value);
786
- console.log(`set -gx ${key} "${renderedValue}"`);
1214
+ console.log(`set -gx ${key} "${escapeFish(value)}"`);
787
1215
  }
788
1216
  break;
789
1217
  case 'bash':
790
1218
  case 'zsh':
791
1219
  case 'sh':
792
1220
  for (const [key, value] of Object.entries(envVars)) {
793
- const renderedValue = value == null ? '' : String(value);
794
- console.log(`export ${key}="${renderedValue}"`);
1221
+ console.log(`export ${key}=${escapePosix(value)}`);
795
1222
  }
796
1223
  break;
797
1224
  case 'powershell':
798
1225
  case 'pwsh':
799
1226
  for (const [key, value] of Object.entries(envVars)) {
800
- const renderedValue = value == null ? '' : String(value);
801
- console.log(`$env:${key}="${renderedValue}"`);
1227
+ console.log(`$env:${key}=${escapePwsh(value)}`);
802
1228
  }
803
1229
  break;
804
1230
  case 'dotenv':
@@ -815,6 +1241,268 @@ function env(format = 'bash') {
815
1241
  }
816
1242
  }
817
1243
 
1244
+ /**
1245
+ * Generate shell completion script
1246
+ */
1247
+ function completion(shell) {
1248
+ if (!shell) {
1249
+ console.error('Error: Missing shell type');
1250
+ console.error('Usage: ccconfig completion <bash|zsh|fish|powershell|pwsh>');
1251
+ console.error('');
1252
+ console.error('To install:');
1253
+ console.error(' Bash: ccconfig completion bash >> ~/.bashrc');
1254
+ console.error(' Zsh: ccconfig completion zsh >> ~/.zshrc');
1255
+ console.error(' Fish: ccconfig completion fish > ~/.config/fish/completions/ccconfig.fish');
1256
+ console.error(' PowerShell: ccconfig completion pwsh >> $PROFILE');
1257
+ process.exit(1);
1258
+ }
1259
+
1260
+ const commands = 'list ls add update use remove rm current mode env edit';
1261
+
1262
+ switch (shell) {
1263
+ case 'bash':
1264
+ console.log(`# ccconfig bash completion
1265
+ _ccconfig_completions() {
1266
+ local cur prev commands profiles
1267
+ COMPREPLY=()
1268
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1269
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1270
+ commands="${commands}"
1271
+
1272
+ # Get available profiles
1273
+ if [ -f ~/.config/ccconfig/profiles.json ]; then
1274
+ profiles=$(node -e "try { const data = require(process.env.HOME + '/.config/ccconfig/profiles.json'); console.log(Object.keys(data.profiles || {}).join(' ')); } catch(e) { }" 2>/dev/null)
1275
+ fi
1276
+
1277
+ case "\${COMP_CWORD}" in
1278
+ 1)
1279
+ COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
1280
+ ;;
1281
+ 2)
1282
+ case "\${prev}" in
1283
+ use|update|remove|rm)
1284
+ COMPREPLY=( $(compgen -W "\${profiles}" -- \${cur}) )
1285
+ ;;
1286
+ mode)
1287
+ COMPREPLY=( $(compgen -W "settings env" -- \${cur}) )
1288
+ ;;
1289
+ env)
1290
+ COMPREPLY=( $(compgen -W "bash zsh fish sh powershell pwsh dotenv" -- \${cur}) )
1291
+ ;;
1292
+ esac
1293
+ ;;
1294
+ 3)
1295
+ case "\${COMP_WORDS[1]}" in
1296
+ use)
1297
+ COMPREPLY=( $(compgen -W "--permanent -p" -- \${cur}) )
1298
+ ;;
1299
+ current)
1300
+ COMPREPLY=( $(compgen -W "--show-secret -s" -- \${cur}) )
1301
+ ;;
1302
+ esac
1303
+ ;;
1304
+ esac
1305
+ }
1306
+
1307
+ complete -F _ccconfig_completions ccconfig
1308
+ `);
1309
+ break;
1310
+
1311
+ case 'zsh':
1312
+ console.log(`# ccconfig zsh completion
1313
+ _ccconfig() {
1314
+ local -a commands profiles modes formats
1315
+ commands=(
1316
+ 'list:List all configurations'
1317
+ 'ls:List all configurations'
1318
+ 'add:Add new configuration'
1319
+ 'update:Update existing configuration'
1320
+ 'use:Switch to specified configuration'
1321
+ 'remove:Remove configuration'
1322
+ 'rm:Remove configuration'
1323
+ 'current:Display current configuration'
1324
+ 'mode:View or switch mode'
1325
+ 'env:Output environment variables'
1326
+ 'edit:Show configuration file location'
1327
+ )
1328
+
1329
+ modes=('settings' 'env')
1330
+ formats=('bash' 'zsh' 'fish' 'sh' 'powershell' 'pwsh' 'dotenv')
1331
+
1332
+ # Get available profiles
1333
+ if [ -f ~/.config/ccconfig/profiles.json ]; then
1334
+ profiles=($(node -e "try { const data = require(process.env.HOME + '/.config/ccconfig/profiles.json'); console.log(Object.keys(data.profiles || {}).join(' ')); } catch(e) { }" 2>/dev/null))
1335
+ fi
1336
+
1337
+ case $CURRENT in
1338
+ 2)
1339
+ _describe 'command' commands
1340
+ ;;
1341
+ 3)
1342
+ case $words[2] in
1343
+ use|update|remove|rm)
1344
+ _describe 'profile' profiles
1345
+ ;;
1346
+ mode)
1347
+ _describe 'mode' modes
1348
+ ;;
1349
+ env)
1350
+ _describe 'format' formats
1351
+ ;;
1352
+ esac
1353
+ ;;
1354
+ 4)
1355
+ case $words[2] in
1356
+ use)
1357
+ _arguments '-p[Write permanently to shell config]' '--permanent[Write permanently to shell config]'
1358
+ ;;
1359
+ current)
1360
+ _arguments '-s[Show full token]' '--show-secret[Show full token]'
1361
+ ;;
1362
+ esac
1363
+ ;;
1364
+ esac
1365
+ }
1366
+
1367
+ compdef _ccconfig ccconfig
1368
+ `);
1369
+ break;
1370
+
1371
+ case 'fish':
1372
+ console.log(`# ccconfig fish completion
1373
+
1374
+ # Commands
1375
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "list" -d "List all configurations"
1376
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "ls" -d "List all configurations"
1377
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "add" -d "Add new configuration"
1378
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "update" -d "Update existing configuration"
1379
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "use" -d "Switch to specified configuration"
1380
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "remove" -d "Remove configuration"
1381
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "rm" -d "Remove configuration"
1382
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "current" -d "Display current configuration"
1383
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "mode" -d "View or switch mode"
1384
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "env" -d "Output environment variables"
1385
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "edit" -d "Show configuration file location"
1386
+
1387
+ # Get profile names dynamically
1388
+ function __ccconfig_profiles
1389
+ if test -f ~/.config/ccconfig/profiles.json
1390
+ node -e "try { const data = require(process.env.HOME + '/.config/ccconfig/profiles.json'); Object.keys(data.profiles || {}).forEach(k => console.log(k)); } catch(e) { }" 2>/dev/null
1391
+ end
1392
+ end
1393
+
1394
+ # Profile name completion for use, update, remove
1395
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from use update remove rm" -a "(__ccconfig_profiles)"
1396
+
1397
+ # Mode options
1398
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from mode" -a "settings env"
1399
+
1400
+ # Env format options
1401
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from env" -a "bash zsh fish sh powershell pwsh dotenv"
1402
+
1403
+ # Flags for use command
1404
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from use" -s p -l permanent -d "Write permanently to shell config"
1405
+
1406
+ # Flags for current command
1407
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from current" -s s -l show-secret -d "Show full token"
1408
+
1409
+ # Global flags
1410
+ complete -c ccconfig -f -s h -l help -d "Display help information"
1411
+ complete -c ccconfig -f -s V -l version -d "Display version information"
1412
+ `);
1413
+ break;
1414
+
1415
+ case 'powershell':
1416
+ case 'pwsh':
1417
+ console.log(`# ccconfig PowerShell completion
1418
+
1419
+ # Get available profiles
1420
+ function Get-CconfigProfiles {
1421
+ $profilesPath = Join-Path $env:USERPROFILE ".config\\ccconfig\\profiles.json"
1422
+ if (Test-Path $profilesPath) {
1423
+ try {
1424
+ $profiles = Get-Content $profilesPath | ConvertFrom-Json
1425
+ return $profiles.profiles.PSObject.Properties.Name
1426
+ } catch {
1427
+ return @()
1428
+ }
1429
+ }
1430
+ return @()
1431
+ }
1432
+
1433
+ # Register argument completer for ccconfig
1434
+ Register-ArgumentCompleter -Native -CommandName ccconfig -ScriptBlock {
1435
+ param($wordToComplete, $commandAst, $cursorPosition)
1436
+
1437
+ $commands = @('list', 'ls', 'add', 'update', 'use', 'remove', 'rm', 'current', 'mode', 'env', 'edit', 'completion')
1438
+ $modes = @('settings', 'env')
1439
+ $formats = @('bash', 'zsh', 'fish', 'sh', 'powershell', 'pwsh', 'dotenv')
1440
+
1441
+ # Parse the command line
1442
+ $tokens = $commandAst.ToString() -split '\\s+'
1443
+ $position = $tokens.Count - 1
1444
+
1445
+ # If we're completing the first argument (command)
1446
+ if ($position -eq 1 -or ($position -eq 2 -and $wordToComplete)) {
1447
+ $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1448
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1449
+ }
1450
+ return
1451
+ }
1452
+
1453
+ # Get the command (first argument)
1454
+ $command = if ($tokens.Count -gt 1) { $tokens[1] } else { '' }
1455
+
1456
+ # Second argument completions based on command
1457
+ if ($position -eq 2 -or ($position -eq 3 -and $wordToComplete)) {
1458
+ switch ($command) {
1459
+ { $_ -in 'use', 'update', 'remove', 'rm' } {
1460
+ Get-CconfigProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1461
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1462
+ }
1463
+ }
1464
+ 'mode' {
1465
+ $modes | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1466
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1467
+ }
1468
+ }
1469
+ 'env' {
1470
+ $formats | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1471
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1472
+ }
1473
+ }
1474
+ 'completion' {
1475
+ @('bash', 'zsh', 'fish', 'powershell', 'pwsh') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1476
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1477
+ }
1478
+ }
1479
+ }
1480
+ return
1481
+ }
1482
+
1483
+ # Flag completions
1484
+ if ($position -ge 3 -and $command -eq 'use') {
1485
+ @('-p', '--permanent') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1486
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', 'Write permanently to shell config')
1487
+ }
1488
+ }
1489
+
1490
+ if ($position -ge 2 -and $command -eq 'current') {
1491
+ @('-s', '--show-secret') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1492
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', 'Show full token')
1493
+ }
1494
+ }
1495
+ }
1496
+ `);
1497
+ break;
1498
+
1499
+ default:
1500
+ console.error(`Error: Unsupported shell: ${shell}`);
1501
+ console.error('Supported shells: bash, zsh, fish, powershell, pwsh');
1502
+ process.exit(1);
1503
+ }
1504
+ }
1505
+
818
1506
  /**
819
1507
  * Display help information
820
1508
  */
@@ -834,9 +1522,9 @@ function help() {
834
1522
  console.log('');
835
1523
  console.log('Global Options:');
836
1524
  console.log(
837
- ' --help, -h Display this help information');
1525
+ ' -h, --help Display this help information');
838
1526
  console.log(
839
- ' --version, -V Display version information');
1527
+ ' -V, --version Display version information');
840
1528
  console.log('');
841
1529
  console.log('Commands:');
842
1530
  console.log(
@@ -844,17 +1532,29 @@ function help() {
844
1532
  console.log(
845
1533
  ' add [name] Add new configuration (interactive)');
846
1534
  console.log(
847
- ' use <name> Switch to specified configuration');
1535
+ ' update [name] Update existing configuration (interactive)');
1536
+ console.log(
1537
+ ' use <name> [-p|--permanent] Switch to specified configuration');
848
1538
  console.log(
849
1539
  ' remove|rm <name> Remove configuration');
850
1540
  console.log(
851
- ' current [--show-secret] Display current configuration');
1541
+ ' current [-s|--show-secret] Display current configuration');
852
1542
  console.log(
853
1543
  ' mode [settings|env] View or switch mode');
854
1544
  console.log(
855
1545
  ' env [format] Output environment variables (env mode)');
856
1546
  console.log(
857
1547
  ' edit Show configuration file location');
1548
+ console.log(
1549
+ ' completion <bash|zsh|fish|pwsh> Generate shell completion script');
1550
+ console.log('');
1551
+ console.log('Flags:');
1552
+ console.log(
1553
+ ' -p, --permanent Write environment variables permanently to shell config');
1554
+ console.log(
1555
+ ' (only effective in env mode with use command)');
1556
+ console.log(
1557
+ ' -s, --show-secret Show full token in current command');
858
1558
  console.log('');
859
1559
  console.log('Configuration file locations:');
860
1560
  console.log(` Configuration list: ${PROFILES_FILE}`);
@@ -878,9 +1578,11 @@ async function main() {
878
1578
  }
879
1579
 
880
1580
  // Extract flags
881
- const showSecret = args.includes('--show-secret');
1581
+ const showSecret = args.includes('--show-secret') || args.includes('-s');
1582
+ const permanent = args.includes('--permanent') || args.includes('-p');
882
1583
  const filteredArgs = args.filter(
883
- arg => arg !== '--show-secret' && arg !== '--version' && arg !== '-V' &&
1584
+ arg => arg !== '--show-secret' && arg !== '-s' && arg !== '--permanent' &&
1585
+ arg !== '-p' && arg !== '--version' && arg !== '-V' &&
884
1586
  arg !== '--help' && arg !== '-h');
885
1587
 
886
1588
  const command = filteredArgs[0];
@@ -893,14 +1595,17 @@ async function main() {
893
1595
  case 'use':
894
1596
  if (!filteredArgs[1]) {
895
1597
  console.error('Error: Missing configuration name');
896
- console.error('Usage: ccconfig use <name>');
1598
+ console.error('Usage: ccconfig use <name> [-p|--permanent]');
897
1599
  process.exit(1);
898
1600
  }
899
- use(filteredArgs[1]);
1601
+ await use(filteredArgs[1], {permanent});
900
1602
  break;
901
1603
  case 'add':
902
1604
  await add(filteredArgs[1]);
903
1605
  break;
1606
+ case 'update':
1607
+ await update(filteredArgs[1]);
1608
+ break;
904
1609
  case 'remove':
905
1610
  case 'rm':
906
1611
  remove(filteredArgs[1]);
@@ -917,6 +1622,9 @@ async function main() {
917
1622
  case 'edit':
918
1623
  edit();
919
1624
  break;
1625
+ case 'completion':
1626
+ completion(filteredArgs[1]);
1627
+ break;
920
1628
  default:
921
1629
  if (!command) {
922
1630
  list();