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.
- package/README.md +198 -160
- package/README_zh.md +406 -0
- package/ccconfig.js +767 -59
- 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.
|
|
327
|
-
console.log(`
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 (
|
|
411
|
+
apiKey = await askQuestion('Please enter ANTHROPIC_API_KEY (press Enter to set as empty)');
|
|
397
412
|
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
if (shellSuggestion.command) {
|
|
955
|
+
if (permanent) {
|
|
956
|
+
console.log('');
|
|
582
957
|
console.log(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1525
|
+
' -h, --help Display this help information');
|
|
838
1526
|
console.log(
|
|
839
|
-
' --version
|
|
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
|
-
'
|
|
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 [
|
|
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 !== '
|
|
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();
|