claude-code-templates 1.17.1 ā 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 +464 -186
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,115 +586,229 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
586
586
|
delete settingConfig.description;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
+
const inquirer = require('inquirer');
|
|
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
|
+
choices: [
|
|
598
|
+
{
|
|
599
|
+
name: 'š User settings (~/.claude/settings.json) - Applies to all projects',
|
|
600
|
+
value: 'user'
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'š Project settings (.claude/settings.json) - Shared with team',
|
|
604
|
+
value: 'project'
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: 'āļø Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
608
|
+
value: 'local',
|
|
609
|
+
checked: true // Default selection
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
name: 'š¢ Enterprise managed settings - System-wide policy (requires admin)',
|
|
613
|
+
value: 'enterprise'
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
validate: function(answer) {
|
|
617
|
+
if (answer.length < 1) {
|
|
618
|
+
return 'You must choose at least one installation location.';
|
|
619
|
+
}
|
|
620
|
+
return true;
|
|
610
621
|
}
|
|
611
|
-
});
|
|
622
|
+
}]);
|
|
623
|
+
|
|
624
|
+
installLocations = selectedLocations;
|
|
612
625
|
}
|
|
613
626
|
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
|
|
635
|
+
if (installLocation === 'user') {
|
|
636
|
+
const os = require('os');
|
|
637
|
+
currentTargetDir = os.homedir();
|
|
638
|
+
settingsFile = 'settings.json';
|
|
639
|
+
} else if (installLocation === 'project') {
|
|
640
|
+
settingsFile = 'settings.json';
|
|
641
|
+
} else if (installLocation === 'local') {
|
|
642
|
+
settingsFile = 'settings.local.json';
|
|
643
|
+
} else if (installLocation === 'enterprise') {
|
|
644
|
+
const os = require('os');
|
|
645
|
+
const platform = os.platform();
|
|
646
|
+
|
|
647
|
+
if (platform === 'darwin') {
|
|
648
|
+
// macOS
|
|
649
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
650
|
+
settingsFile = 'managed-settings.json';
|
|
651
|
+
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
652
|
+
// Linux and WSL
|
|
653
|
+
currentTargetDir = '/etc/claude-code';
|
|
654
|
+
settingsFile = 'managed-settings.json';
|
|
655
|
+
} else if (platform === 'win32') {
|
|
656
|
+
// Windows
|
|
657
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
658
|
+
settingsFile = 'managed-settings.json';
|
|
659
|
+
} else {
|
|
660
|
+
console.log(chalk.yellow('ā ļø Platform not supported for enterprise settings. Using user settings instead.'));
|
|
661
|
+
const os = require('os');
|
|
662
|
+
currentTargetDir = os.homedir();
|
|
663
|
+
settingsFile = 'settings.json';
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
console.log(chalk.yellow(`ā ļø Enterprise settings require administrator privileges.`));
|
|
667
|
+
console.log(chalk.gray(`š Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
619
668
|
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
// Ask user about conflicts if any exist and not in silent mode
|
|
623
|
-
if (conflicts.length > 0 && !options.silent) {
|
|
624
|
-
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing setting "${settingName}":`));
|
|
625
|
-
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
626
669
|
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
message: 'Do you want to overwrite the existing configuration?',
|
|
632
|
-
default: false
|
|
633
|
-
}]);
|
|
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 = {};
|
|
634
674
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
// Ensure .claude directory exists for regular settings
|
|
687
|
+
await fs.ensureDir(claudeDir);
|
|
638
688
|
}
|
|
639
|
-
} else if (conflicts.length > 0 && options.silent) {
|
|
640
|
-
// In silent mode (batch installation), skip conflicting settings and warn
|
|
641
|
-
console.log(chalk.yellow(`ā ļø Skipping setting "${settingName}" due to conflicts (use individual installation to resolve)`));
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Deep merge configurations
|
|
646
|
-
const mergedConfig = {
|
|
647
|
-
...existingConfig,
|
|
648
|
-
...settingConfig
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
// Deep merge specific sections (only if no conflicts or user approved overwrite)
|
|
652
|
-
if (existingConfig.permissions && settingConfig.permissions) {
|
|
653
|
-
mergedConfig.permissions = {
|
|
654
|
-
...existingConfig.permissions,
|
|
655
|
-
...settingConfig.permissions
|
|
656
|
-
};
|
|
657
689
|
|
|
658
|
-
//
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
+
}
|
|
699
|
+
|
|
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
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
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]}")`);
|
|
664
717
|
}
|
|
665
718
|
});
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
|
679
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++;
|
|
680
799
|
}
|
|
681
800
|
|
|
682
|
-
//
|
|
683
|
-
await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
|
|
684
|
-
|
|
801
|
+
// Summary after all installations
|
|
685
802
|
if (!options.silent) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
+
}
|
|
689
810
|
}
|
|
690
811
|
|
|
691
|
-
// Track successful setting installation
|
|
692
|
-
trackingService.trackDownload('setting', settingName, {
|
|
693
|
-
installation_type: 'individual_setting',
|
|
694
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
695
|
-
source: 'github_main'
|
|
696
|
-
});
|
|
697
|
-
|
|
698
812
|
} catch (error) {
|
|
699
813
|
console.log(chalk.red(`ā Error installing setting: ${error.message}`));
|
|
700
814
|
}
|
|
@@ -734,110 +848,224 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
734
848
|
delete hookConfig.description;
|
|
735
849
|
}
|
|
736
850
|
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
let existingConfig = {};
|
|
741
|
-
|
|
742
|
-
// Ensure .claude directory exists
|
|
743
|
-
await fs.ensureDir(claudeDir);
|
|
744
|
-
|
|
745
|
-
if (await fs.pathExists(targetSettingsFile)) {
|
|
746
|
-
existingConfig = await fs.readJson(targetSettingsFile);
|
|
747
|
-
console.log(chalk.yellow('š Existing .claude/settings.json found, merging hook configurations...'));
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Check for conflicts before merging (simplified for new array format)
|
|
751
|
-
const conflicts = [];
|
|
752
|
-
|
|
753
|
-
// For the new array format, we'll allow appending rather than conflict detection
|
|
754
|
-
// This is because Claude Code's array format naturally supports multiple hooks
|
|
755
|
-
// Conflicts are less likely and generally hooks can coexist
|
|
756
|
-
|
|
757
|
-
// Ask user about conflicts if any exist and not in silent mode
|
|
758
|
-
if (conflicts.length > 0 && !options.silent) {
|
|
759
|
-
console.log(chalk.yellow(`\nā ļø Conflicts detected while installing hook "${hookName}":`));
|
|
760
|
-
conflicts.forEach(conflict => console.log(chalk.gray(` ⢠${conflict}`)));
|
|
761
|
-
|
|
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) {
|
|
762
854
|
const inquirer = require('inquirer');
|
|
763
|
-
const {
|
|
764
|
-
type: '
|
|
765
|
-
name: '
|
|
766
|
-
message: '
|
|
767
|
-
|
|
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)',
|
|
859
|
+
choices: [
|
|
860
|
+
{
|
|
861
|
+
name: 'š User settings (~/.claude/settings.json) - Applies to all projects',
|
|
862
|
+
value: 'user'
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: 'š Project settings (.claude/settings.json) - Shared with team',
|
|
866
|
+
value: 'project'
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: 'āļø Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
870
|
+
value: 'local',
|
|
871
|
+
checked: true // Default selection
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: 'š¢ Enterprise managed settings - System-wide policy (requires admin)',
|
|
875
|
+
value: 'enterprise'
|
|
876
|
+
}
|
|
877
|
+
],
|
|
878
|
+
validate: function(answer) {
|
|
879
|
+
if (answer.length < 1) {
|
|
880
|
+
return 'You must choose at least one installation location.';
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
768
884
|
}]);
|
|
769
885
|
|
|
770
|
-
|
|
771
|
-
console.log(chalk.yellow(`ā¹ļø Installation of hook "${hookName}" cancelled by user.`));
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
} else if (conflicts.length > 0 && options.silent) {
|
|
775
|
-
// In silent mode (batch installation), skip conflicting hooks and warn
|
|
776
|
-
console.log(chalk.yellow(`ā ļø Skipping hook "${hookName}" due to conflicts (use individual installation to resolve)`));
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Deep merge configurations with proper hook array structure
|
|
781
|
-
const mergedConfig = {
|
|
782
|
-
...existingConfig
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
// Initialize hooks structure if it doesn't exist
|
|
786
|
-
if (!mergedConfig.hooks) {
|
|
787
|
-
mergedConfig.hooks = {};
|
|
886
|
+
installLocations = selectedLocations;
|
|
788
887
|
}
|
|
789
888
|
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
|
896
|
+
|
|
897
|
+
if (installLocation === 'user') {
|
|
898
|
+
const os = require('os');
|
|
899
|
+
currentTargetDir = os.homedir();
|
|
900
|
+
settingsFile = 'settings.json';
|
|
901
|
+
} else if (installLocation === 'project') {
|
|
902
|
+
settingsFile = 'settings.json';
|
|
903
|
+
} else if (installLocation === 'local') {
|
|
904
|
+
settingsFile = 'settings.local.json';
|
|
905
|
+
} else if (installLocation === 'enterprise') {
|
|
906
|
+
const os = require('os');
|
|
907
|
+
const platform = os.platform();
|
|
908
|
+
|
|
909
|
+
if (platform === 'darwin') {
|
|
910
|
+
// macOS
|
|
911
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
912
|
+
settingsFile = 'managed-settings.json';
|
|
913
|
+
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
914
|
+
// Linux and WSL
|
|
915
|
+
currentTargetDir = '/etc/claude-code';
|
|
916
|
+
settingsFile = 'managed-settings.json';
|
|
917
|
+
} else if (platform === 'win32') {
|
|
918
|
+
// Windows
|
|
919
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
920
|
+
settingsFile = 'managed-settings.json';
|
|
796
921
|
} else {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
922
|
+
console.log(chalk.yellow('ā ļø Platform not supported for enterprise settings. Using user settings instead.'));
|
|
923
|
+
const os = require('os');
|
|
924
|
+
currentTargetDir = os.homedir();
|
|
925
|
+
settingsFile = 'settings.json';
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
console.log(chalk.yellow(`ā ļø Enterprise settings require administrator privileges.`));
|
|
929
|
+
console.log(chalk.gray(`š Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
930
|
+
}
|
|
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);
|
|
950
|
+
}
|
|
951
|
+
|
|
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
|
+
}
|
|
961
|
+
|
|
962
|
+
// Check for conflicts before merging (simplified for new array format)
|
|
963
|
+
const conflicts = [];
|
|
964
|
+
|
|
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
|
|
990
|
+
}
|
|
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];
|
|
806
1008
|
} else {
|
|
807
|
-
//
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
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
|
+
});
|
|
811
1032
|
}
|
|
812
|
-
// Add old format hook as a single matcher
|
|
813
|
-
mergedConfig.hooks[hookType].push({
|
|
814
|
-
matcher: "*",
|
|
815
|
-
hooks: [{
|
|
816
|
-
type: "command",
|
|
817
|
-
command: hookConfig.hooks[hookType]
|
|
818
|
-
}]
|
|
819
|
-
});
|
|
820
1033
|
}
|
|
821
|
-
}
|
|
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'
|
|
822
1052
|
});
|
|
1053
|
+
|
|
1054
|
+
// Increment successful installations counter
|
|
1055
|
+
successfulInstallations++;
|
|
823
1056
|
}
|
|
824
1057
|
|
|
825
|
-
//
|
|
826
|
-
await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
|
|
827
|
-
|
|
1058
|
+
// Summary after all installations
|
|
828
1059
|
if (!options.silent) {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
+
}
|
|
832
1067
|
}
|
|
833
1068
|
|
|
834
|
-
// Track successful hook installation
|
|
835
|
-
trackingService.trackDownload('hook', hookName, {
|
|
836
|
-
installation_type: 'individual_hook',
|
|
837
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
838
|
-
source: 'github_main'
|
|
839
|
-
});
|
|
840
|
-
|
|
841
1069
|
} catch (error) {
|
|
842
1070
|
console.log(chalk.red(`ā Error installing hook: ${error.message}`));
|
|
843
1071
|
}
|
|
@@ -980,6 +1208,48 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
980
1208
|
console.log(chalk.gray(` Settings: ${components.settings.length}`));
|
|
981
1209
|
console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
|
|
982
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
|
+
|
|
983
1253
|
// Install agents
|
|
984
1254
|
for (const agent of components.agents) {
|
|
985
1255
|
console.log(chalk.gray(` Installing agent: ${agent}`));
|
|
@@ -998,16 +1268,24 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
998
1268
|
await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
|
|
999
1269
|
}
|
|
1000
1270
|
|
|
1001
|
-
// Install settings
|
|
1271
|
+
// Install settings (using shared installation locations)
|
|
1002
1272
|
for (const setting of components.settings) {
|
|
1003
1273
|
console.log(chalk.gray(` Installing setting: ${setting}`));
|
|
1004
|
-
await installIndividualSetting(setting, targetDir, {
|
|
1274
|
+
await installIndividualSetting(setting, targetDir, {
|
|
1275
|
+
...options,
|
|
1276
|
+
silent: true,
|
|
1277
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1278
|
+
});
|
|
1005
1279
|
}
|
|
1006
1280
|
|
|
1007
|
-
// Install hooks
|
|
1281
|
+
// Install hooks (using shared installation locations)
|
|
1008
1282
|
for (const hook of components.hooks) {
|
|
1009
1283
|
console.log(chalk.gray(` Installing hook: ${hook}`));
|
|
1010
|
-
await installIndividualHook(hook, targetDir, {
|
|
1284
|
+
await installIndividualHook(hook, targetDir, {
|
|
1285
|
+
...options,
|
|
1286
|
+
silent: true,
|
|
1287
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1288
|
+
});
|
|
1011
1289
|
}
|
|
1012
1290
|
|
|
1013
1291
|
// Handle YAML workflow if provided
|