claude-code-templates 1.18.0 ā 1.19.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/package.json +1 -1
- package/src/index.js +371 -257
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -586,14 +586,14 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
586
586
|
delete settingConfig.description;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
//
|
|
590
|
-
let
|
|
591
|
-
if (!options.silent) {
|
|
589
|
+
// Use shared locations if provided (batch mode), otherwise ask user
|
|
590
|
+
let installLocations = options.sharedInstallLocations || ['local']; // default to local settings
|
|
591
|
+
if (!options.silent && !options.sharedInstallLocations) {
|
|
592
592
|
const inquirer = require('inquirer');
|
|
593
|
-
const {
|
|
594
|
-
type: '
|
|
595
|
-
name: '
|
|
596
|
-
message: 'Where would you like to install this setting?',
|
|
593
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
594
|
+
type: 'checkbox',
|
|
595
|
+
name: 'selectedLocations',
|
|
596
|
+
message: 'Where would you like to install this setting? (Select one or more)',
|
|
597
597
|
choices: [
|
|
598
598
|
{
|
|
599
599
|
name: 'š User settings (~/.claude/settings.json) - Applies to all projects',
|
|
@@ -605,19 +605,36 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
605
605
|
},
|
|
606
606
|
{
|
|
607
607
|
name: 'āļø Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
608
|
-
value: 'local'
|
|
608
|
+
value: 'local',
|
|
609
|
+
checked: true // Default selection
|
|
609
610
|
},
|
|
610
611
|
{
|
|
611
612
|
name: 'š¢ Enterprise managed settings - System-wide policy (requires admin)',
|
|
612
613
|
value: 'enterprise'
|
|
613
614
|
}
|
|
614
615
|
],
|
|
615
|
-
|
|
616
|
+
validate: function(answer) {
|
|
617
|
+
if (answer.length < 1) {
|
|
618
|
+
return 'You must choose at least one installation location.';
|
|
619
|
+
}
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
616
622
|
}]);
|
|
617
|
-
|
|
623
|
+
|
|
624
|
+
installLocations = selectedLocations;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Install the setting in each selected location
|
|
628
|
+
let successfulInstallations = 0;
|
|
629
|
+
for (const installLocation of installLocations) {
|
|
630
|
+
console.log(chalk.blue(`\nš Installing "${settingName}" in ${installLocation} settings...`));
|
|
631
|
+
|
|
632
|
+
let currentTargetDir = targetDir;
|
|
633
|
+
let settingsFile = 'settings.local.json'; // default
|
|
634
|
+
|
|
618
635
|
if (installLocation === 'user') {
|
|
619
636
|
const os = require('os');
|
|
620
|
-
|
|
637
|
+
currentTargetDir = os.homedir();
|
|
621
638
|
settingsFile = 'settings.json';
|
|
622
639
|
} else if (installLocation === 'project') {
|
|
623
640
|
settingsFile = 'settings.json';
|
|
@@ -629,154 +646,169 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
629
646
|
|
|
630
647
|
if (platform === 'darwin') {
|
|
631
648
|
// macOS
|
|
632
|
-
|
|
649
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
633
650
|
settingsFile = 'managed-settings.json';
|
|
634
651
|
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
635
652
|
// Linux and WSL
|
|
636
|
-
|
|
653
|
+
currentTargetDir = '/etc/claude-code';
|
|
637
654
|
settingsFile = 'managed-settings.json';
|
|
638
655
|
} else if (platform === 'win32') {
|
|
639
656
|
// Windows
|
|
640
|
-
|
|
657
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
641
658
|
settingsFile = 'managed-settings.json';
|
|
642
659
|
} else {
|
|
643
660
|
console.log(chalk.yellow('ā ļø Platform not supported for enterprise settings. Using user settings instead.'));
|
|
644
661
|
const os = require('os');
|
|
645
|
-
|
|
662
|
+
currentTargetDir = os.homedir();
|
|
646
663
|
settingsFile = 'settings.json';
|
|
647
664
|
}
|
|
648
665
|
|
|
649
666
|
console.log(chalk.yellow(`ā ļø Enterprise settings require administrator privileges.`));
|
|
650
|
-
console.log(chalk.gray(`š Target path: ${path.join(
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Determine target directory and file based on selection
|
|
655
|
-
const claudeDir = path.join(targetDir, '.claude');
|
|
656
|
-
const targetSettingsFile = path.join(claudeDir, settingsFile);
|
|
657
|
-
let existingConfig = {};
|
|
658
|
-
|
|
659
|
-
// For enterprise settings, create directory structure directly (not under .claude)
|
|
660
|
-
if (settingsFile === 'managed-settings.json') {
|
|
661
|
-
// Ensure enterprise directory exists (requires admin privileges)
|
|
662
|
-
try {
|
|
663
|
-
await fs.ensureDir(targetDir);
|
|
664
|
-
} catch (error) {
|
|
665
|
-
console.log(chalk.red(`ā Failed to create enterprise directory: ${error.message}`));
|
|
666
|
-
console.log(chalk.yellow('š” Try running with administrator privileges or choose a different installation location.'));
|
|
667
|
-
return;
|
|
667
|
+
console.log(chalk.gray(`š Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
668
668
|
}
|
|
669
|
-
} else {
|
|
670
|
-
// Ensure .claude directory exists for regular settings
|
|
671
|
-
await fs.ensureDir(claudeDir);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Read existing configuration
|
|
675
|
-
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
676
|
-
? path.join(targetDir, settingsFile)
|
|
677
|
-
: targetSettingsFile;
|
|
678
669
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
670
|
+
// Determine target directory and file based on selection
|
|
671
|
+
const claudeDir = path.join(currentTargetDir, '.claude');
|
|
672
|
+
const targetSettingsFile = path.join(claudeDir, settingsFile);
|
|
673
|
+
let existingConfig = {};
|
|
674
|
+
|
|
675
|
+
// For enterprise settings, create directory structure directly (not under .claude)
|
|
676
|
+
if (settingsFile === 'managed-settings.json') {
|
|
677
|
+
// Ensure enterprise directory exists (requires admin privileges)
|
|
678
|
+
try {
|
|
679
|
+
await fs.ensureDir(currentTargetDir);
|
|
680
|
+
} catch (error) {
|
|
681
|
+
console.log(chalk.red(`ā Failed to create enterprise directory: ${error.message}`));
|
|
682
|
+
console.log(chalk.yellow('š” Try running with administrator privileges or choose a different installation location.'));
|
|
683
|
+
continue; // Skip this location and continue with others
|
|
692
684
|
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
// Check for conflicting top-level settings
|
|
697
|
-
Object.keys(settingConfig).forEach(key => {
|
|
698
|
-
if (key !== 'permissions' && key !== 'env' && key !== 'hooks' &&
|
|
699
|
-
existingConfig[key] !== undefined && existingConfig[key] !== settingConfig[key]) {
|
|
700
|
-
conflicts.push(`Setting "${key}" (current: "${existingConfig[key]}", new: "${settingConfig[key]}")`);
|
|
685
|
+
} else {
|
|
686
|
+
// Ensure .claude directory exists for regular settings
|
|
687
|
+
await fs.ensureDir(claudeDir);
|
|
701
688
|
}
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
// Ask user about conflicts if any exist and not in silent mode
|
|
705
|
-
if (conflicts.length > 0 && !options.silent) {
|
|
706
|
-
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing setting "${settingName}":`));
|
|
707
|
-
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
708
689
|
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
690
|
+
// Read existing configuration
|
|
691
|
+
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
692
|
+
? path.join(currentTargetDir, settingsFile)
|
|
693
|
+
: targetSettingsFile;
|
|
694
|
+
|
|
695
|
+
if (await fs.pathExists(actualTargetFile)) {
|
|
696
|
+
existingConfig = await fs.readJson(actualTargetFile);
|
|
697
|
+
console.log(chalk.yellow(`š Existing ${settingsFile} found, merging configurations...`));
|
|
698
|
+
}
|
|
716
699
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
700
|
+
// Check for conflicts before merging
|
|
701
|
+
const conflicts = [];
|
|
702
|
+
|
|
703
|
+
// Check for conflicting environment variables
|
|
704
|
+
if (existingConfig.env && settingConfig.env) {
|
|
705
|
+
Object.keys(settingConfig.env).forEach(key => {
|
|
706
|
+
if (existingConfig.env[key] && existingConfig.env[key] !== settingConfig.env[key]) {
|
|
707
|
+
conflicts.push(`Environment variable "${key}" (current: "${existingConfig.env[key]}", new: "${settingConfig.env[key]}")`);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
720
710
|
}
|
|
721
|
-
} else if (conflicts.length > 0 && options.silent) {
|
|
722
|
-
// In silent mode (batch installation), skip conflicting settings and warn
|
|
723
|
-
console.log(chalk.yellow(`ā ļø Skipping setting "${settingName}" due to conflicts (use individual installation to resolve)`));
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Deep merge configurations
|
|
728
|
-
const mergedConfig = {
|
|
729
|
-
...existingConfig,
|
|
730
|
-
...settingConfig
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
// Deep merge specific sections (only if no conflicts or user approved overwrite)
|
|
734
|
-
if (existingConfig.permissions && settingConfig.permissions) {
|
|
735
|
-
mergedConfig.permissions = {
|
|
736
|
-
...existingConfig.permissions,
|
|
737
|
-
...settingConfig.permissions
|
|
738
|
-
};
|
|
739
711
|
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
if (
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
];
|
|
712
|
+
// Check for conflicting top-level settings
|
|
713
|
+
Object.keys(settingConfig).forEach(key => {
|
|
714
|
+
if (key !== 'permissions' && key !== 'env' && key !== 'hooks' &&
|
|
715
|
+
existingConfig[key] !== undefined && existingConfig[key] !== settingConfig[key]) {
|
|
716
|
+
conflicts.push(`Setting "${key}" (current: "${existingConfig[key]}", new: "${settingConfig[key]}")`);
|
|
746
717
|
}
|
|
747
718
|
});
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
719
|
+
|
|
720
|
+
// Ask user about conflicts if any exist and not in silent mode
|
|
721
|
+
if (conflicts.length > 0 && !options.silent) {
|
|
722
|
+
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing setting "${settingName}" in ${installLocation}:`));
|
|
723
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
724
|
+
|
|
725
|
+
const inquirer = require('inquirer');
|
|
726
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
727
|
+
type: 'confirm',
|
|
728
|
+
name: 'shouldOverwrite',
|
|
729
|
+
message: `Do you want to overwrite the existing configuration in ${installLocation}?`,
|
|
730
|
+
default: false
|
|
731
|
+
}]);
|
|
732
|
+
|
|
733
|
+
if (!shouldOverwrite) {
|
|
734
|
+
console.log(chalk.yellow(`ā¹ļø Installation of setting "${settingName}" in ${installLocation} cancelled by user.`));
|
|
735
|
+
continue; // Skip this location and continue with others
|
|
736
|
+
}
|
|
737
|
+
} else if (conflicts.length > 0 && options.silent) {
|
|
738
|
+
// In silent mode (batch installation), skip conflicting settings and warn
|
|
739
|
+
console.log(chalk.yellow(`ā ļø Skipping setting "${settingName}" in ${installLocation} due to conflicts (use individual installation to resolve)`));
|
|
740
|
+
continue; // Skip this location and continue with others
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Deep merge configurations
|
|
744
|
+
const mergedConfig = {
|
|
745
|
+
...existingConfig,
|
|
746
|
+
...settingConfig
|
|
761
747
|
};
|
|
748
|
+
|
|
749
|
+
// Deep merge specific sections (only if no conflicts or user approved overwrite)
|
|
750
|
+
if (existingConfig.permissions && settingConfig.permissions) {
|
|
751
|
+
mergedConfig.permissions = {
|
|
752
|
+
...existingConfig.permissions,
|
|
753
|
+
...settingConfig.permissions
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// Merge arrays for allow, deny, ask (no conflicts here, just merge)
|
|
757
|
+
['allow', 'deny', 'ask'].forEach(key => {
|
|
758
|
+
if (existingConfig.permissions[key] && settingConfig.permissions[key]) {
|
|
759
|
+
mergedConfig.permissions[key] = [
|
|
760
|
+
...new Set([...existingConfig.permissions[key], ...settingConfig.permissions[key]])
|
|
761
|
+
];
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (existingConfig.env && settingConfig.env) {
|
|
767
|
+
mergedConfig.env = {
|
|
768
|
+
...existingConfig.env,
|
|
769
|
+
...settingConfig.env
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (existingConfig.hooks && settingConfig.hooks) {
|
|
774
|
+
mergedConfig.hooks = {
|
|
775
|
+
...existingConfig.hooks,
|
|
776
|
+
...settingConfig.hooks
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Write the merged configuration
|
|
781
|
+
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
782
|
+
|
|
783
|
+
if (!options.silent) {
|
|
784
|
+
console.log(chalk.green(`ā
Setting "${settingName}" installed successfully in ${installLocation}!`));
|
|
785
|
+
console.log(chalk.cyan(`š Configuration merged into: ${actualTargetFile}`));
|
|
786
|
+
console.log(chalk.cyan(`š¦ Downloaded from: ${githubUrl}`));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Track successful setting installation for this location
|
|
790
|
+
trackingService.trackDownload('setting', settingName, {
|
|
791
|
+
installation_type: 'individual_setting',
|
|
792
|
+
installation_location: installLocation,
|
|
793
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
794
|
+
source: 'github_main'
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Increment successful installations counter
|
|
798
|
+
successfulInstallations++;
|
|
762
799
|
}
|
|
763
800
|
|
|
764
|
-
//
|
|
765
|
-
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
766
|
-
|
|
801
|
+
// Summary after all installations
|
|
767
802
|
if (!options.silent) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
803
|
+
if (successfulInstallations === installLocations.length) {
|
|
804
|
+
console.log(chalk.green(`\nš Setting "${settingName}" successfully installed in ${successfulInstallations} location(s)!`));
|
|
805
|
+
} else {
|
|
806
|
+
console.log(chalk.yellow(`\nā ļø Setting "${settingName}" installed in ${successfulInstallations} of ${installLocations.length} location(s).`));
|
|
807
|
+
const failedCount = installLocations.length - successfulInstallations;
|
|
808
|
+
console.log(chalk.red(`ā ${failedCount} installation(s) failed due to permission or other errors.`));
|
|
809
|
+
}
|
|
771
810
|
}
|
|
772
811
|
|
|
773
|
-
// Track successful setting installation
|
|
774
|
-
trackingService.trackDownload('setting', settingName, {
|
|
775
|
-
installation_type: 'individual_setting',
|
|
776
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
777
|
-
source: 'github_main'
|
|
778
|
-
});
|
|
779
|
-
|
|
780
812
|
} catch (error) {
|
|
781
813
|
console.log(chalk.red(`ā Error installing setting: ${error.message}`));
|
|
782
814
|
}
|
|
@@ -816,14 +848,14 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
816
848
|
delete hookConfig.description;
|
|
817
849
|
}
|
|
818
850
|
|
|
819
|
-
//
|
|
820
|
-
let
|
|
821
|
-
if (!options.silent) {
|
|
851
|
+
// Use shared locations if provided (batch mode), otherwise ask user
|
|
852
|
+
let installLocations = options.sharedInstallLocations || ['local']; // default to local settings
|
|
853
|
+
if (!options.silent && !options.sharedInstallLocations) {
|
|
822
854
|
const inquirer = require('inquirer');
|
|
823
|
-
const {
|
|
824
|
-
type: '
|
|
825
|
-
name: '
|
|
826
|
-
message: 'Where would you like to install this hook?',
|
|
855
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
856
|
+
type: 'checkbox',
|
|
857
|
+
name: 'selectedLocations',
|
|
858
|
+
message: 'Where would you like to install this hook? (Select one or more)',
|
|
827
859
|
choices: [
|
|
828
860
|
{
|
|
829
861
|
name: 'š User settings (~/.claude/settings.json) - Applies to all projects',
|
|
@@ -835,19 +867,36 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
835
867
|
},
|
|
836
868
|
{
|
|
837
869
|
name: 'āļø Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
838
|
-
value: 'local'
|
|
870
|
+
value: 'local',
|
|
871
|
+
checked: true // Default selection
|
|
839
872
|
},
|
|
840
873
|
{
|
|
841
874
|
name: 'š¢ Enterprise managed settings - System-wide policy (requires admin)',
|
|
842
875
|
value: 'enterprise'
|
|
843
876
|
}
|
|
844
877
|
],
|
|
845
|
-
|
|
878
|
+
validate: function(answer) {
|
|
879
|
+
if (answer.length < 1) {
|
|
880
|
+
return 'You must choose at least one installation location.';
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
846
884
|
}]);
|
|
885
|
+
|
|
886
|
+
installLocations = selectedLocations;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Install the hook in each selected location
|
|
890
|
+
let successfulInstallations = 0;
|
|
891
|
+
for (const installLocation of installLocations) {
|
|
892
|
+
console.log(chalk.blue(`\nš Installing "${hookName}" in ${installLocation} settings...`));
|
|
893
|
+
|
|
894
|
+
let currentTargetDir = targetDir;
|
|
895
|
+
let settingsFile = 'settings.local.json'; // default
|
|
847
896
|
|
|
848
897
|
if (installLocation === 'user') {
|
|
849
898
|
const os = require('os');
|
|
850
|
-
|
|
899
|
+
currentTargetDir = os.homedir();
|
|
851
900
|
settingsFile = 'settings.json';
|
|
852
901
|
} else if (installLocation === 'project') {
|
|
853
902
|
settingsFile = 'settings.json';
|
|
@@ -859,149 +908,164 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
859
908
|
|
|
860
909
|
if (platform === 'darwin') {
|
|
861
910
|
// macOS
|
|
862
|
-
|
|
911
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
863
912
|
settingsFile = 'managed-settings.json';
|
|
864
913
|
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
865
914
|
// Linux and WSL
|
|
866
|
-
|
|
915
|
+
currentTargetDir = '/etc/claude-code';
|
|
867
916
|
settingsFile = 'managed-settings.json';
|
|
868
917
|
} else if (platform === 'win32') {
|
|
869
918
|
// Windows
|
|
870
|
-
|
|
919
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
871
920
|
settingsFile = 'managed-settings.json';
|
|
872
921
|
} else {
|
|
873
922
|
console.log(chalk.yellow('ā ļø Platform not supported for enterprise settings. Using user settings instead.'));
|
|
874
923
|
const os = require('os');
|
|
875
|
-
|
|
924
|
+
currentTargetDir = os.homedir();
|
|
876
925
|
settingsFile = 'settings.json';
|
|
877
926
|
}
|
|
878
927
|
|
|
879
928
|
console.log(chalk.yellow(`ā ļø Enterprise settings require administrator privileges.`));
|
|
880
|
-
console.log(chalk.gray(`š Target path: ${path.join(
|
|
929
|
+
console.log(chalk.gray(`š Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
881
930
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
931
|
+
|
|
932
|
+
// Determine target directory and file based on selection
|
|
933
|
+
const claudeDir = path.join(currentTargetDir, '.claude');
|
|
934
|
+
const targetSettingsFile = path.join(claudeDir, settingsFile);
|
|
935
|
+
let existingConfig = {};
|
|
936
|
+
|
|
937
|
+
// For enterprise settings, create directory structure directly (not under .claude)
|
|
938
|
+
if (settingsFile === 'managed-settings.json') {
|
|
939
|
+
// Ensure enterprise directory exists (requires admin privileges)
|
|
940
|
+
try {
|
|
941
|
+
await fs.ensureDir(currentTargetDir);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.log(chalk.red(`ā Failed to create enterprise directory: ${error.message}`));
|
|
944
|
+
console.log(chalk.yellow('š” Try running with administrator privileges or choose a different installation location.'));
|
|
945
|
+
continue; // Skip this location and continue with others
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
// Ensure .claude directory exists for regular settings
|
|
949
|
+
await fs.ensureDir(claudeDir);
|
|
898
950
|
}
|
|
899
|
-
} else {
|
|
900
|
-
// Ensure .claude directory exists for regular settings
|
|
901
|
-
await fs.ensureDir(claudeDir);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Read existing configuration
|
|
905
|
-
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
906
|
-
? path.join(targetDir, settingsFile)
|
|
907
|
-
: targetSettingsFile;
|
|
908
951
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
// This is because Claude Code's array format naturally supports multiple hooks
|
|
919
|
-
// Conflicts are less likely and generally hooks can coexist
|
|
920
|
-
|
|
921
|
-
// Ask user about conflicts if any exist and not in silent mode
|
|
922
|
-
if (conflicts.length > 0 && !options.silent) {
|
|
923
|
-
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing hook "${hookName}":`));
|
|
924
|
-
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
952
|
+
// Read existing configuration
|
|
953
|
+
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
954
|
+
? path.join(currentTargetDir, settingsFile)
|
|
955
|
+
: targetSettingsFile;
|
|
956
|
+
|
|
957
|
+
if (await fs.pathExists(actualTargetFile)) {
|
|
958
|
+
existingConfig = await fs.readJson(actualTargetFile);
|
|
959
|
+
console.log(chalk.yellow(`š Existing ${settingsFile} found, merging hook configurations...`));
|
|
960
|
+
}
|
|
925
961
|
|
|
926
|
-
|
|
927
|
-
const
|
|
928
|
-
type: 'confirm',
|
|
929
|
-
name: 'shouldOverwrite',
|
|
930
|
-
message: 'Do you want to overwrite the existing hook configuration?',
|
|
931
|
-
default: false
|
|
932
|
-
}]);
|
|
962
|
+
// Check for conflicts before merging (simplified for new array format)
|
|
963
|
+
const conflicts = [];
|
|
933
964
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
965
|
+
// For the new array format, we'll allow appending rather than conflict detection
|
|
966
|
+
// This is because Claude Code's array format naturally supports multiple hooks
|
|
967
|
+
// Conflicts are less likely and generally hooks can coexist
|
|
968
|
+
|
|
969
|
+
// Ask user about conflicts if any exist and not in silent mode
|
|
970
|
+
if (conflicts.length > 0 && !options.silent) {
|
|
971
|
+
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing hook "${hookName}" in ${installLocation}:`));
|
|
972
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
973
|
+
|
|
974
|
+
const inquirer = require('inquirer');
|
|
975
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
976
|
+
type: 'confirm',
|
|
977
|
+
name: 'shouldOverwrite',
|
|
978
|
+
message: `Do you want to overwrite the existing hook configuration in ${installLocation}?`,
|
|
979
|
+
default: false
|
|
980
|
+
}]);
|
|
981
|
+
|
|
982
|
+
if (!shouldOverwrite) {
|
|
983
|
+
console.log(chalk.yellow(`ā¹ļø Installation of hook "${hookName}" in ${installLocation} cancelled by user.`));
|
|
984
|
+
continue; // Skip this location and continue with others
|
|
985
|
+
}
|
|
986
|
+
} else if (conflicts.length > 0 && options.silent) {
|
|
987
|
+
// In silent mode (batch installation), skip conflicting hooks and warn
|
|
988
|
+
console.log(chalk.yellow(`ā ļø Skipping hook "${hookName}" in ${installLocation} due to conflicts (use individual installation to resolve)`));
|
|
989
|
+
continue; // Skip this location and continue with others
|
|
937
990
|
}
|
|
938
|
-
|
|
939
|
-
//
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (hookConfig.hooks) {
|
|
956
|
-
Object.keys(hookConfig.hooks).forEach(hookType => {
|
|
957
|
-
if (!mergedConfig.hooks[hookType]) {
|
|
958
|
-
// If hook type doesn't exist, just copy the array
|
|
959
|
-
mergedConfig.hooks[hookType] = hookConfig.hooks[hookType];
|
|
960
|
-
} else {
|
|
961
|
-
// If hook type exists, append to the array (Claude Code format)
|
|
962
|
-
if (Array.isArray(hookConfig.hooks[hookType])) {
|
|
963
|
-
// New format: array of hook objects
|
|
964
|
-
if (!Array.isArray(mergedConfig.hooks[hookType])) {
|
|
965
|
-
// Convert old format to new format
|
|
966
|
-
mergedConfig.hooks[hookType] = [];
|
|
967
|
-
}
|
|
968
|
-
// Append new hooks to existing array
|
|
969
|
-
mergedConfig.hooks[hookType] = mergedConfig.hooks[hookType].concat(hookConfig.hooks[hookType]);
|
|
991
|
+
|
|
992
|
+
// Deep merge configurations with proper hook array structure
|
|
993
|
+
const mergedConfig = {
|
|
994
|
+
...existingConfig
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// Initialize hooks structure if it doesn't exist
|
|
998
|
+
if (!mergedConfig.hooks) {
|
|
999
|
+
mergedConfig.hooks = {};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Merge hook configurations properly (Claude Code expects arrays)
|
|
1003
|
+
if (hookConfig.hooks) {
|
|
1004
|
+
Object.keys(hookConfig.hooks).forEach(hookType => {
|
|
1005
|
+
if (!mergedConfig.hooks[hookType]) {
|
|
1006
|
+
// If hook type doesn't exist, just copy the array
|
|
1007
|
+
mergedConfig.hooks[hookType] = hookConfig.hooks[hookType];
|
|
970
1008
|
} else {
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
mergedConfig.hooks[hookType]
|
|
1009
|
+
// If hook type exists, append to the array (Claude Code format)
|
|
1010
|
+
if (Array.isArray(hookConfig.hooks[hookType])) {
|
|
1011
|
+
// New format: array of hook objects
|
|
1012
|
+
if (!Array.isArray(mergedConfig.hooks[hookType])) {
|
|
1013
|
+
// Convert old format to new format
|
|
1014
|
+
mergedConfig.hooks[hookType] = [];
|
|
1015
|
+
}
|
|
1016
|
+
// Append new hooks to existing array
|
|
1017
|
+
mergedConfig.hooks[hookType] = mergedConfig.hooks[hookType].concat(hookConfig.hooks[hookType]);
|
|
1018
|
+
} else {
|
|
1019
|
+
// Old format compatibility: convert to new format
|
|
1020
|
+
console.log(chalk.yellow(`ā ļø Converting old hook format to new Claude Code format for ${hookType}`));
|
|
1021
|
+
if (!Array.isArray(mergedConfig.hooks[hookType])) {
|
|
1022
|
+
mergedConfig.hooks[hookType] = [];
|
|
1023
|
+
}
|
|
1024
|
+
// Add old format hook as a single matcher
|
|
1025
|
+
mergedConfig.hooks[hookType].push({
|
|
1026
|
+
matcher: "*",
|
|
1027
|
+
hooks: [{
|
|
1028
|
+
type: "command",
|
|
1029
|
+
command: hookConfig.hooks[hookType]
|
|
1030
|
+
}]
|
|
1031
|
+
});
|
|
975
1032
|
}
|
|
976
|
-
// Add old format hook as a single matcher
|
|
977
|
-
mergedConfig.hooks[hookType].push({
|
|
978
|
-
matcher: "*",
|
|
979
|
-
hooks: [{
|
|
980
|
-
type: "command",
|
|
981
|
-
command: hookConfig.hooks[hookType]
|
|
982
|
-
}]
|
|
983
|
-
});
|
|
984
1033
|
}
|
|
985
|
-
}
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Write the merged configuration
|
|
1038
|
+
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
1039
|
+
|
|
1040
|
+
if (!options.silent) {
|
|
1041
|
+
console.log(chalk.green(`ā
Hook "${hookName}" installed successfully in ${installLocation}!`));
|
|
1042
|
+
console.log(chalk.cyan(`š Configuration merged into: ${actualTargetFile}`));
|
|
1043
|
+
console.log(chalk.cyan(`š¦ Downloaded from: ${githubUrl}`));
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Track successful hook installation for this location
|
|
1047
|
+
trackingService.trackDownload('hook', hookName, {
|
|
1048
|
+
installation_type: 'individual_hook',
|
|
1049
|
+
installation_location: installLocation,
|
|
1050
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
1051
|
+
source: 'github_main'
|
|
986
1052
|
});
|
|
1053
|
+
|
|
1054
|
+
// Increment successful installations counter
|
|
1055
|
+
successfulInstallations++;
|
|
987
1056
|
}
|
|
988
1057
|
|
|
989
|
-
//
|
|
990
|
-
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
991
|
-
|
|
1058
|
+
// Summary after all installations
|
|
992
1059
|
if (!options.silent) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1060
|
+
if (successfulInstallations === installLocations.length) {
|
|
1061
|
+
console.log(chalk.green(`\nš Hook "${hookName}" successfully installed in ${successfulInstallations} location(s)!`));
|
|
1062
|
+
} else {
|
|
1063
|
+
console.log(chalk.yellow(`\nā ļø Hook "${hookName}" installed in ${successfulInstallations} of ${installLocations.length} location(s).`));
|
|
1064
|
+
const failedCount = installLocations.length - successfulInstallations;
|
|
1065
|
+
console.log(chalk.red(`ā ${failedCount} installation(s) failed due to permission or other errors.`));
|
|
1066
|
+
}
|
|
996
1067
|
}
|
|
997
1068
|
|
|
998
|
-
// Track successful hook installation
|
|
999
|
-
trackingService.trackDownload('hook', hookName, {
|
|
1000
|
-
installation_type: 'individual_hook',
|
|
1001
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
1002
|
-
source: 'github_main'
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
1069
|
} catch (error) {
|
|
1006
1070
|
console.log(chalk.red(`ā Error installing hook: ${error.message}`));
|
|
1007
1071
|
}
|
|
@@ -1144,6 +1208,48 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1144
1208
|
console.log(chalk.gray(` Settings: ${components.settings.length}`));
|
|
1145
1209
|
console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
|
|
1146
1210
|
|
|
1211
|
+
// Ask for installation locations once for configuration components (if any exist and not in silent mode)
|
|
1212
|
+
let sharedInstallLocations = ['local']; // default
|
|
1213
|
+
const hasSettingsOrHooks = components.settings.length > 0 || components.hooks.length > 0;
|
|
1214
|
+
|
|
1215
|
+
if (hasSettingsOrHooks && !options.yes) {
|
|
1216
|
+
console.log(chalk.blue('\nš Choose installation locations for configuration components:'));
|
|
1217
|
+
const inquirer = require('inquirer');
|
|
1218
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
1219
|
+
type: 'checkbox',
|
|
1220
|
+
name: 'selectedLocations',
|
|
1221
|
+
message: 'Where would you like to install the configuration components? (Select one or more)',
|
|
1222
|
+
choices: [
|
|
1223
|
+
{
|
|
1224
|
+
name: 'š User settings (~/.claude/settings.json) - Applies to all projects',
|
|
1225
|
+
value: 'user'
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
name: 'š Project settings (.claude/settings.json) - Shared with team',
|
|
1229
|
+
value: 'project'
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
name: 'āļø Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
1233
|
+
value: 'local',
|
|
1234
|
+
checked: true // Default selection
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
name: 'š¢ Enterprise managed settings - System-wide policy (requires admin)',
|
|
1238
|
+
value: 'enterprise'
|
|
1239
|
+
}
|
|
1240
|
+
],
|
|
1241
|
+
validate: function(answer) {
|
|
1242
|
+
if (answer.length < 1) {
|
|
1243
|
+
return 'You must choose at least one installation location.';
|
|
1244
|
+
}
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
}]);
|
|
1248
|
+
|
|
1249
|
+
sharedInstallLocations = selectedLocations;
|
|
1250
|
+
console.log(chalk.cyan(`š Will install configuration components in: ${sharedInstallLocations.join(', ')}`));
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1147
1253
|
// Install agents
|
|
1148
1254
|
for (const agent of components.agents) {
|
|
1149
1255
|
console.log(chalk.gray(` Installing agent: ${agent}`));
|
|
@@ -1162,16 +1268,24 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1162
1268
|
await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
|
|
1163
1269
|
}
|
|
1164
1270
|
|
|
1165
|
-
// Install settings
|
|
1271
|
+
// Install settings (using shared installation locations)
|
|
1166
1272
|
for (const setting of components.settings) {
|
|
1167
1273
|
console.log(chalk.gray(` Installing setting: ${setting}`));
|
|
1168
|
-
await installIndividualSetting(setting, targetDir, {
|
|
1274
|
+
await installIndividualSetting(setting, targetDir, {
|
|
1275
|
+
...options,
|
|
1276
|
+
silent: true,
|
|
1277
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1278
|
+
});
|
|
1169
1279
|
}
|
|
1170
1280
|
|
|
1171
|
-
// Install hooks
|
|
1281
|
+
// Install hooks (using shared installation locations)
|
|
1172
1282
|
for (const hook of components.hooks) {
|
|
1173
1283
|
console.log(chalk.gray(` Installing hook: ${hook}`));
|
|
1174
|
-
await installIndividualHook(hook, targetDir, {
|
|
1284
|
+
await installIndividualHook(hook, targetDir, {
|
|
1285
|
+
...options,
|
|
1286
|
+
silent: true,
|
|
1287
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1288
|
+
});
|
|
1175
1289
|
}
|
|
1176
1290
|
|
|
1177
1291
|
// Handle YAML workflow if provided
|