claude-code-templates 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +224 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.17.0",
3
+ "version": "1.18.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,17 +586,99 @@ async function installIndividualSetting(settingName, targetDir, options) {
586
586
  delete settingConfig.description;
587
587
  }
588
588
 
589
- // Check if .claude/settings.json exists in target directory
589
+ // Ask user where to install the setting (unless in silent mode)
590
+ let settingsFile = 'settings.json'; // default
591
+ if (!options.silent) {
592
+ const inquirer = require('inquirer');
593
+ const { installLocation } = await inquirer.prompt([{
594
+ type: 'list',
595
+ name: 'installLocation',
596
+ message: 'Where would you like to install this setting?',
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
+ },
610
+ {
611
+ name: '🏢 Enterprise managed settings - System-wide policy (requires admin)',
612
+ value: 'enterprise'
613
+ }
614
+ ],
615
+ default: 'local'
616
+ }]);
617
+
618
+ if (installLocation === 'user') {
619
+ const os = require('os');
620
+ targetDir = os.homedir();
621
+ settingsFile = 'settings.json';
622
+ } else if (installLocation === 'project') {
623
+ settingsFile = 'settings.json';
624
+ } else if (installLocation === 'local') {
625
+ settingsFile = 'settings.local.json';
626
+ } else if (installLocation === 'enterprise') {
627
+ const os = require('os');
628
+ const platform = os.platform();
629
+
630
+ if (platform === 'darwin') {
631
+ // macOS
632
+ targetDir = '/Library/Application Support/ClaudeCode';
633
+ settingsFile = 'managed-settings.json';
634
+ } else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
635
+ // Linux and WSL
636
+ targetDir = '/etc/claude-code';
637
+ settingsFile = 'managed-settings.json';
638
+ } else if (platform === 'win32') {
639
+ // Windows
640
+ targetDir = 'C:\\ProgramData\\ClaudeCode';
641
+ settingsFile = 'managed-settings.json';
642
+ } else {
643
+ console.log(chalk.yellow('⚠️ Platform not supported for enterprise settings. Using user settings instead.'));
644
+ const os = require('os');
645
+ targetDir = os.homedir();
646
+ settingsFile = 'settings.json';
647
+ }
648
+
649
+ console.log(chalk.yellow(`⚠️ Enterprise settings require administrator privileges.`));
650
+ console.log(chalk.gray(`📍 Target path: ${path.join(targetDir, settingsFile)}`));
651
+ }
652
+ }
653
+
654
+ // Determine target directory and file based on selection
590
655
  const claudeDir = path.join(targetDir, '.claude');
591
- const targetSettingsFile = path.join(claudeDir, 'settings.json');
656
+ const targetSettingsFile = path.join(claudeDir, settingsFile);
592
657
  let existingConfig = {};
593
658
 
594
- // Ensure .claude directory exists
595
- await fs.ensureDir(claudeDir);
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;
668
+ }
669
+ } else {
670
+ // Ensure .claude directory exists for regular settings
671
+ await fs.ensureDir(claudeDir);
672
+ }
596
673
 
597
- if (await fs.pathExists(targetSettingsFile)) {
598
- existingConfig = await fs.readJson(targetSettingsFile);
599
- console.log(chalk.yellow('📝 Existing .claude/settings.json found, merging configurations...'));
674
+ // Read existing configuration
675
+ const actualTargetFile = settingsFile === 'managed-settings.json'
676
+ ? path.join(targetDir, settingsFile)
677
+ : targetSettingsFile;
678
+
679
+ if (await fs.pathExists(actualTargetFile)) {
680
+ existingConfig = await fs.readJson(actualTargetFile);
681
+ console.log(chalk.yellow(`📝 Existing ${settingsFile} found, merging configurations...`));
600
682
  }
601
683
 
602
684
  // Check for conflicts before merging
@@ -680,11 +762,11 @@ async function installIndividualSetting(settingName, targetDir, options) {
680
762
  }
681
763
 
682
764
  // Write the merged configuration
683
- await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
765
+ await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
684
766
 
685
767
  if (!options.silent) {
686
768
  console.log(chalk.green(`✅ Setting "${settingName}" installed successfully!`));
687
- console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetSettingsFile)}`));
769
+ console.log(chalk.cyan(`📁 Configuration merged into: ${actualTargetFile}`));
688
770
  console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
689
771
  }
690
772
 
@@ -734,35 +816,107 @@ async function installIndividualHook(hookName, targetDir, options) {
734
816
  delete hookConfig.description;
735
817
  }
736
818
 
737
- // Check if .claude/settings.json exists in target directory (hooks go in settings.json)
819
+ // Ask user where to install the hook (unless in silent mode)
820
+ let settingsFile = 'settings.json'; // default
821
+ if (!options.silent) {
822
+ const inquirer = require('inquirer');
823
+ const { installLocation } = await inquirer.prompt([{
824
+ type: 'list',
825
+ name: 'installLocation',
826
+ message: 'Where would you like to install this hook?',
827
+ choices: [
828
+ {
829
+ name: '🏠 User settings (~/.claude/settings.json) - Applies to all projects',
830
+ value: 'user'
831
+ },
832
+ {
833
+ name: '📁 Project settings (.claude/settings.json) - Shared with team',
834
+ value: 'project'
835
+ },
836
+ {
837
+ name: '⚙️ Local settings (.claude/settings.local.json) - Personal, not committed',
838
+ value: 'local'
839
+ },
840
+ {
841
+ name: '🏢 Enterprise managed settings - System-wide policy (requires admin)',
842
+ value: 'enterprise'
843
+ }
844
+ ],
845
+ default: 'local'
846
+ }]);
847
+
848
+ if (installLocation === 'user') {
849
+ const os = require('os');
850
+ targetDir = os.homedir();
851
+ settingsFile = 'settings.json';
852
+ } else if (installLocation === 'project') {
853
+ settingsFile = 'settings.json';
854
+ } else if (installLocation === 'local') {
855
+ settingsFile = 'settings.local.json';
856
+ } else if (installLocation === 'enterprise') {
857
+ const os = require('os');
858
+ const platform = os.platform();
859
+
860
+ if (platform === 'darwin') {
861
+ // macOS
862
+ targetDir = '/Library/Application Support/ClaudeCode';
863
+ settingsFile = 'managed-settings.json';
864
+ } else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
865
+ // Linux and WSL
866
+ targetDir = '/etc/claude-code';
867
+ settingsFile = 'managed-settings.json';
868
+ } else if (platform === 'win32') {
869
+ // Windows
870
+ targetDir = 'C:\\ProgramData\\ClaudeCode';
871
+ settingsFile = 'managed-settings.json';
872
+ } else {
873
+ console.log(chalk.yellow('⚠️ Platform not supported for enterprise settings. Using user settings instead.'));
874
+ const os = require('os');
875
+ targetDir = os.homedir();
876
+ settingsFile = 'settings.json';
877
+ }
878
+
879
+ console.log(chalk.yellow(`⚠️ Enterprise settings require administrator privileges.`));
880
+ console.log(chalk.gray(`📍 Target path: ${path.join(targetDir, settingsFile)}`));
881
+ }
882
+ }
883
+
884
+ // Determine target directory and file based on selection
738
885
  const claudeDir = path.join(targetDir, '.claude');
739
- const targetSettingsFile = path.join(claudeDir, 'settings.json');
886
+ const targetSettingsFile = path.join(claudeDir, settingsFile);
740
887
  let existingConfig = {};
741
888
 
742
- // Ensure .claude directory exists
743
- await fs.ensureDir(claudeDir);
889
+ // For enterprise settings, create directory structure directly (not under .claude)
890
+ if (settingsFile === 'managed-settings.json') {
891
+ // Ensure enterprise directory exists (requires admin privileges)
892
+ try {
893
+ await fs.ensureDir(targetDir);
894
+ } catch (error) {
895
+ console.log(chalk.red(`❌ Failed to create enterprise directory: ${error.message}`));
896
+ console.log(chalk.yellow('💡 Try running with administrator privileges or choose a different installation location.'));
897
+ return;
898
+ }
899
+ } else {
900
+ // Ensure .claude directory exists for regular settings
901
+ await fs.ensureDir(claudeDir);
902
+ }
744
903
 
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...'));
904
+ // Read existing configuration
905
+ const actualTargetFile = settingsFile === 'managed-settings.json'
906
+ ? path.join(targetDir, settingsFile)
907
+ : targetSettingsFile;
908
+
909
+ if (await fs.pathExists(actualTargetFile)) {
910
+ existingConfig = await fs.readJson(actualTargetFile);
911
+ console.log(chalk.yellow(`📝 Existing ${settingsFile} found, merging hook configurations...`));
748
912
  }
749
913
 
750
- // Check for conflicts before merging
914
+ // Check for conflicts before merging (simplified for new array format)
751
915
  const conflicts = [];
752
916
 
753
- // Check for conflicting hooks
754
- if (existingConfig.hooks && hookConfig.hooks) {
755
- ['PreToolUse', 'PostToolUse'].forEach(hookType => {
756
- if (existingConfig.hooks[hookType] && hookConfig.hooks[hookType]) {
757
- Object.keys(hookConfig.hooks[hookType]).forEach(toolName => {
758
- if (existingConfig.hooks[hookType][toolName] &&
759
- existingConfig.hooks[hookType][toolName] !== hookConfig.hooks[hookType][toolName]) {
760
- conflicts.push(`Hook "${hookType}.${toolName}" (current: "${existingConfig.hooks[hookType][toolName]}", new: "${hookConfig.hooks[hookType][toolName]}")`);
761
- }
762
- });
763
- }
764
- });
765
- }
917
+ // For the new array format, we'll allow appending rather than conflict detection
918
+ // This is because Claude Code's array format naturally supports multiple hooks
919
+ // Conflicts are less likely and generally hooks can coexist
766
920
 
767
921
  // Ask user about conflicts if any exist and not in silent mode
768
922
  if (conflicts.length > 0 && !options.silent) {
@@ -787,36 +941,57 @@ async function installIndividualHook(hookName, targetDir, options) {
787
941
  return;
788
942
  }
789
943
 
790
- // Deep merge configurations
944
+ // Deep merge configurations with proper hook array structure
791
945
  const mergedConfig = {
792
- ...existingConfig,
793
- ...hookConfig
946
+ ...existingConfig
794
947
  };
795
948
 
796
- // Deep merge hooks specifically (only if no conflicts or user approved overwrite)
797
- if (existingConfig.hooks && hookConfig.hooks) {
798
- mergedConfig.hooks = {
799
- ...existingConfig.hooks,
800
- ...hookConfig.hooks
801
- };
802
-
803
- // Deep merge hook types (PreToolUse, PostToolUse)
804
- ['PreToolUse', 'PostToolUse'].forEach(hookType => {
805
- if (existingConfig.hooks[hookType] && hookConfig.hooks[hookType]) {
806
- mergedConfig.hooks[hookType] = {
807
- ...existingConfig.hooks[hookType],
808
- ...hookConfig.hooks[hookType]
809
- };
949
+ // Initialize hooks structure if it doesn't exist
950
+ if (!mergedConfig.hooks) {
951
+ mergedConfig.hooks = {};
952
+ }
953
+
954
+ // Merge hook configurations properly (Claude Code expects arrays)
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]);
970
+ } else {
971
+ // Old format compatibility: convert to new format
972
+ console.log(chalk.yellow(`⚠️ Converting old hook format to new Claude Code format for ${hookType}`));
973
+ if (!Array.isArray(mergedConfig.hooks[hookType])) {
974
+ mergedConfig.hooks[hookType] = [];
975
+ }
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
+ }
810
985
  }
811
986
  });
812
987
  }
813
988
 
814
989
  // Write the merged configuration
815
- await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
990
+ await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
816
991
 
817
992
  if (!options.silent) {
818
993
  console.log(chalk.green(`✅ Hook "${hookName}" installed successfully!`));
819
- console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetSettingsFile)}`));
994
+ console.log(chalk.cyan(`📁 Configuration merged into: ${actualTargetFile}`));
820
995
  console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
821
996
  }
822
997