claude-code-templates 1.18.0 → 1.19.1
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 +2 -2
- package/src/index.js +404 -270
- package/src/tracking-service.js +62 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
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": {
|
|
@@ -90,6 +90,6 @@
|
|
|
90
90
|
],
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"jest": "^30.0.4",
|
|
93
|
-
"jest-watch-typeahead": "^
|
|
93
|
+
"jest-watch-typeahead": "^3.0.1"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/src/index.js
CHANGED
|
@@ -401,8 +401,11 @@ async function installIndividualAgent(agentName, targetDir, options) {
|
|
|
401
401
|
source: 'github_main'
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
+
return true;
|
|
405
|
+
|
|
404
406
|
} catch (error) {
|
|
405
407
|
console.log(chalk.red(`❌ Error installing agent: ${error.message}`));
|
|
408
|
+
return false;
|
|
406
409
|
}
|
|
407
410
|
}
|
|
408
411
|
|
|
@@ -464,8 +467,11 @@ async function installIndividualCommand(commandName, targetDir, options) {
|
|
|
464
467
|
source: 'github_main'
|
|
465
468
|
});
|
|
466
469
|
|
|
470
|
+
return true;
|
|
471
|
+
|
|
467
472
|
} catch (error) {
|
|
468
473
|
console.log(chalk.red(`❌ Error installing command: ${error.message}`));
|
|
474
|
+
return false;
|
|
469
475
|
}
|
|
470
476
|
}
|
|
471
477
|
|
|
@@ -547,8 +553,11 @@ async function installIndividualMCP(mcpName, targetDir, options) {
|
|
|
547
553
|
source: 'github_main'
|
|
548
554
|
});
|
|
549
555
|
|
|
556
|
+
return true;
|
|
557
|
+
|
|
550
558
|
} catch (error) {
|
|
551
559
|
console.log(chalk.red(`❌ Error installing MCP: ${error.message}`));
|
|
560
|
+
return false;
|
|
552
561
|
}
|
|
553
562
|
}
|
|
554
563
|
|
|
@@ -586,14 +595,14 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
586
595
|
delete settingConfig.description;
|
|
587
596
|
}
|
|
588
597
|
|
|
589
|
-
//
|
|
590
|
-
let
|
|
591
|
-
if (!options.silent) {
|
|
598
|
+
// Use shared locations if provided (batch mode), otherwise ask user
|
|
599
|
+
let installLocations = options.sharedInstallLocations || ['local']; // default to local settings
|
|
600
|
+
if (!options.silent && !options.sharedInstallLocations) {
|
|
592
601
|
const inquirer = require('inquirer');
|
|
593
|
-
const {
|
|
594
|
-
type: '
|
|
595
|
-
name: '
|
|
596
|
-
message: 'Where would you like to install this setting?',
|
|
602
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
603
|
+
type: 'checkbox',
|
|
604
|
+
name: 'selectedLocations',
|
|
605
|
+
message: 'Where would you like to install this setting? (Select one or more)',
|
|
597
606
|
choices: [
|
|
598
607
|
{
|
|
599
608
|
name: '🏠 User settings (~/.claude/settings.json) - Applies to all projects',
|
|
@@ -605,19 +614,36 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
605
614
|
},
|
|
606
615
|
{
|
|
607
616
|
name: '⚙️ Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
608
|
-
value: 'local'
|
|
617
|
+
value: 'local',
|
|
618
|
+
checked: true // Default selection
|
|
609
619
|
},
|
|
610
620
|
{
|
|
611
621
|
name: '🏢 Enterprise managed settings - System-wide policy (requires admin)',
|
|
612
622
|
value: 'enterprise'
|
|
613
623
|
}
|
|
614
624
|
],
|
|
615
|
-
|
|
625
|
+
validate: function(answer) {
|
|
626
|
+
if (answer.length < 1) {
|
|
627
|
+
return 'You must choose at least one installation location.';
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
616
631
|
}]);
|
|
617
|
-
|
|
632
|
+
|
|
633
|
+
installLocations = selectedLocations;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Install the setting in each selected location
|
|
637
|
+
let successfulInstallations = 0;
|
|
638
|
+
for (const installLocation of installLocations) {
|
|
639
|
+
console.log(chalk.blue(`\n📍 Installing "${settingName}" in ${installLocation} settings...`));
|
|
640
|
+
|
|
641
|
+
let currentTargetDir = targetDir;
|
|
642
|
+
let settingsFile = 'settings.local.json'; // default
|
|
643
|
+
|
|
618
644
|
if (installLocation === 'user') {
|
|
619
645
|
const os = require('os');
|
|
620
|
-
|
|
646
|
+
currentTargetDir = os.homedir();
|
|
621
647
|
settingsFile = 'settings.json';
|
|
622
648
|
} else if (installLocation === 'project') {
|
|
623
649
|
settingsFile = 'settings.json';
|
|
@@ -629,156 +655,177 @@ async function installIndividualSetting(settingName, targetDir, options) {
|
|
|
629
655
|
|
|
630
656
|
if (platform === 'darwin') {
|
|
631
657
|
// macOS
|
|
632
|
-
|
|
658
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
633
659
|
settingsFile = 'managed-settings.json';
|
|
634
660
|
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
635
661
|
// Linux and WSL
|
|
636
|
-
|
|
662
|
+
currentTargetDir = '/etc/claude-code';
|
|
637
663
|
settingsFile = 'managed-settings.json';
|
|
638
664
|
} else if (platform === 'win32') {
|
|
639
665
|
// Windows
|
|
640
|
-
|
|
666
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
641
667
|
settingsFile = 'managed-settings.json';
|
|
642
668
|
} else {
|
|
643
669
|
console.log(chalk.yellow('⚠️ Platform not supported for enterprise settings. Using user settings instead.'));
|
|
644
670
|
const os = require('os');
|
|
645
|
-
|
|
671
|
+
currentTargetDir = os.homedir();
|
|
646
672
|
settingsFile = 'settings.json';
|
|
647
673
|
}
|
|
648
674
|
|
|
649
675
|
console.log(chalk.yellow(`⚠️ Enterprise settings require administrator privileges.`));
|
|
650
|
-
console.log(chalk.gray(`📍 Target path: ${path.join(
|
|
676
|
+
console.log(chalk.gray(`📍 Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
651
677
|
}
|
|
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;
|
|
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
678
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
679
|
+
// Determine target directory and file based on selection
|
|
680
|
+
const claudeDir = path.join(currentTargetDir, '.claude');
|
|
681
|
+
const targetSettingsFile = path.join(claudeDir, settingsFile);
|
|
682
|
+
let existingConfig = {};
|
|
683
|
+
|
|
684
|
+
// For enterprise settings, create directory structure directly (not under .claude)
|
|
685
|
+
if (settingsFile === 'managed-settings.json') {
|
|
686
|
+
// Ensure enterprise directory exists (requires admin privileges)
|
|
687
|
+
try {
|
|
688
|
+
await fs.ensureDir(currentTargetDir);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
console.log(chalk.red(`❌ Failed to create enterprise directory: ${error.message}`));
|
|
691
|
+
console.log(chalk.yellow('💡 Try running with administrator privileges or choose a different installation location.'));
|
|
692
|
+
continue; // Skip this location and continue with others
|
|
692
693
|
}
|
|
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]}")`);
|
|
694
|
+
} else {
|
|
695
|
+
// Ensure .claude directory exists for regular settings
|
|
696
|
+
await fs.ensureDir(claudeDir);
|
|
701
697
|
}
|
|
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
698
|
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
699
|
+
// Read existing configuration
|
|
700
|
+
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
701
|
+
? path.join(currentTargetDir, settingsFile)
|
|
702
|
+
: targetSettingsFile;
|
|
703
|
+
|
|
704
|
+
if (await fs.pathExists(actualTargetFile)) {
|
|
705
|
+
existingConfig = await fs.readJson(actualTargetFile);
|
|
706
|
+
console.log(chalk.yellow(`📝 Existing ${settingsFile} found, merging configurations...`));
|
|
707
|
+
}
|
|
716
708
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
709
|
+
// Check for conflicts before merging
|
|
710
|
+
const conflicts = [];
|
|
711
|
+
|
|
712
|
+
// Check for conflicting environment variables
|
|
713
|
+
if (existingConfig.env && settingConfig.env) {
|
|
714
|
+
Object.keys(settingConfig.env).forEach(key => {
|
|
715
|
+
if (existingConfig.env[key] && existingConfig.env[key] !== settingConfig.env[key]) {
|
|
716
|
+
conflicts.push(`Environment variable "${key}" (current: "${existingConfig.env[key]}", new: "${settingConfig.env[key]}")`);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
720
719
|
}
|
|
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
720
|
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
if (
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
721
|
+
// Check for conflicting top-level settings
|
|
722
|
+
Object.keys(settingConfig).forEach(key => {
|
|
723
|
+
if (key !== 'permissions' && key !== 'env' && key !== 'hooks' &&
|
|
724
|
+
existingConfig[key] !== undefined && JSON.stringify(existingConfig[key]) !== JSON.stringify(settingConfig[key])) {
|
|
725
|
+
|
|
726
|
+
// For objects, just indicate the setting name without showing the complex values
|
|
727
|
+
if (typeof existingConfig[key] === 'object' && existingConfig[key] !== null &&
|
|
728
|
+
typeof settingConfig[key] === 'object' && settingConfig[key] !== null) {
|
|
729
|
+
conflicts.push(`Setting "${key}" (will be overwritten with new configuration)`);
|
|
730
|
+
} else {
|
|
731
|
+
conflicts.push(`Setting "${key}" (current: "${existingConfig[key]}", new: "${settingConfig[key]}")`);
|
|
732
|
+
}
|
|
746
733
|
}
|
|
747
734
|
});
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
735
|
+
|
|
736
|
+
// Ask user about conflicts if any exist
|
|
737
|
+
if (conflicts.length > 0) {
|
|
738
|
+
console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing setting "${settingName}" in ${installLocation}:`));
|
|
739
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
|
|
740
|
+
|
|
741
|
+
const inquirer = require('inquirer');
|
|
742
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
743
|
+
type: 'confirm',
|
|
744
|
+
name: 'shouldOverwrite',
|
|
745
|
+
message: `Do you want to overwrite the existing configuration in ${installLocation}?`,
|
|
746
|
+
default: false
|
|
747
|
+
}]);
|
|
748
|
+
|
|
749
|
+
if (!shouldOverwrite) {
|
|
750
|
+
console.log(chalk.yellow(`⏹️ Installation of setting "${settingName}" in ${installLocation} cancelled by user.`));
|
|
751
|
+
continue; // Skip this location and continue with others
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Deep merge configurations
|
|
756
|
+
const mergedConfig = {
|
|
757
|
+
...existingConfig,
|
|
758
|
+
...settingConfig
|
|
761
759
|
};
|
|
760
|
+
|
|
761
|
+
// Deep merge specific sections (only if no conflicts or user approved overwrite)
|
|
762
|
+
if (existingConfig.permissions && settingConfig.permissions) {
|
|
763
|
+
mergedConfig.permissions = {
|
|
764
|
+
...existingConfig.permissions,
|
|
765
|
+
...settingConfig.permissions
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
// Merge arrays for allow, deny, ask (no conflicts here, just merge)
|
|
769
|
+
['allow', 'deny', 'ask'].forEach(key => {
|
|
770
|
+
if (existingConfig.permissions[key] && settingConfig.permissions[key]) {
|
|
771
|
+
mergedConfig.permissions[key] = [
|
|
772
|
+
...new Set([...existingConfig.permissions[key], ...settingConfig.permissions[key]])
|
|
773
|
+
];
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (existingConfig.env && settingConfig.env) {
|
|
779
|
+
mergedConfig.env = {
|
|
780
|
+
...existingConfig.env,
|
|
781
|
+
...settingConfig.env
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (existingConfig.hooks && settingConfig.hooks) {
|
|
786
|
+
mergedConfig.hooks = {
|
|
787
|
+
...existingConfig.hooks,
|
|
788
|
+
...settingConfig.hooks
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Write the merged configuration
|
|
793
|
+
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
794
|
+
|
|
795
|
+
if (!options.silent) {
|
|
796
|
+
console.log(chalk.green(`✅ Setting "${settingName}" installed successfully in ${installLocation}!`));
|
|
797
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${actualTargetFile}`));
|
|
798
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Track successful setting installation for this location
|
|
802
|
+
trackingService.trackDownload('setting', settingName, {
|
|
803
|
+
installation_type: 'individual_setting',
|
|
804
|
+
installation_location: installLocation,
|
|
805
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
806
|
+
source: 'github_main'
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Increment successful installations counter
|
|
810
|
+
successfulInstallations++;
|
|
762
811
|
}
|
|
763
812
|
|
|
764
|
-
//
|
|
765
|
-
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
766
|
-
|
|
813
|
+
// Summary after all installations
|
|
767
814
|
if (!options.silent) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
815
|
+
if (successfulInstallations === installLocations.length) {
|
|
816
|
+
console.log(chalk.green(`\n🎉 Setting "${settingName}" successfully installed in ${successfulInstallations} location(s)!`));
|
|
817
|
+
} else {
|
|
818
|
+
console.log(chalk.yellow(`\n⚠️ Setting "${settingName}" installed in ${successfulInstallations} of ${installLocations.length} location(s).`));
|
|
819
|
+
const failedCount = installLocations.length - successfulInstallations;
|
|
820
|
+
console.log(chalk.red(`❌ ${failedCount} installation(s) failed due to permission or other errors.`));
|
|
821
|
+
}
|
|
771
822
|
}
|
|
772
823
|
|
|
773
|
-
|
|
774
|
-
trackingService.trackDownload('setting', settingName, {
|
|
775
|
-
installation_type: 'individual_setting',
|
|
776
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
777
|
-
source: 'github_main'
|
|
778
|
-
});
|
|
824
|
+
return successfulInstallations;
|
|
779
825
|
|
|
780
826
|
} catch (error) {
|
|
781
827
|
console.log(chalk.red(`❌ Error installing setting: ${error.message}`));
|
|
828
|
+
return 0;
|
|
782
829
|
}
|
|
783
830
|
}
|
|
784
831
|
|
|
@@ -816,14 +863,14 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
816
863
|
delete hookConfig.description;
|
|
817
864
|
}
|
|
818
865
|
|
|
819
|
-
//
|
|
820
|
-
let
|
|
821
|
-
if (!options.silent) {
|
|
866
|
+
// Use shared locations if provided (batch mode), otherwise ask user
|
|
867
|
+
let installLocations = options.sharedInstallLocations || ['local']; // default to local settings
|
|
868
|
+
if (!options.silent && !options.sharedInstallLocations) {
|
|
822
869
|
const inquirer = require('inquirer');
|
|
823
|
-
const {
|
|
824
|
-
type: '
|
|
825
|
-
name: '
|
|
826
|
-
message: 'Where would you like to install this hook?',
|
|
870
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
871
|
+
type: 'checkbox',
|
|
872
|
+
name: 'selectedLocations',
|
|
873
|
+
message: 'Where would you like to install this hook? (Select one or more)',
|
|
827
874
|
choices: [
|
|
828
875
|
{
|
|
829
876
|
name: '🏠 User settings (~/.claude/settings.json) - Applies to all projects',
|
|
@@ -835,19 +882,36 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
835
882
|
},
|
|
836
883
|
{
|
|
837
884
|
name: '⚙️ Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
838
|
-
value: 'local'
|
|
885
|
+
value: 'local',
|
|
886
|
+
checked: true // Default selection
|
|
839
887
|
},
|
|
840
888
|
{
|
|
841
889
|
name: '🏢 Enterprise managed settings - System-wide policy (requires admin)',
|
|
842
890
|
value: 'enterprise'
|
|
843
891
|
}
|
|
844
892
|
],
|
|
845
|
-
|
|
893
|
+
validate: function(answer) {
|
|
894
|
+
if (answer.length < 1) {
|
|
895
|
+
return 'You must choose at least one installation location.';
|
|
896
|
+
}
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
846
899
|
}]);
|
|
900
|
+
|
|
901
|
+
installLocations = selectedLocations;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Install the hook in each selected location
|
|
905
|
+
let successfulInstallations = 0;
|
|
906
|
+
for (const installLocation of installLocations) {
|
|
907
|
+
console.log(chalk.blue(`\n📍 Installing "${hookName}" in ${installLocation} settings...`));
|
|
908
|
+
|
|
909
|
+
let currentTargetDir = targetDir;
|
|
910
|
+
let settingsFile = 'settings.local.json'; // default
|
|
847
911
|
|
|
848
912
|
if (installLocation === 'user') {
|
|
849
913
|
const os = require('os');
|
|
850
|
-
|
|
914
|
+
currentTargetDir = os.homedir();
|
|
851
915
|
settingsFile = 'settings.json';
|
|
852
916
|
} else if (installLocation === 'project') {
|
|
853
917
|
settingsFile = 'settings.json';
|
|
@@ -859,151 +923,165 @@ async function installIndividualHook(hookName, targetDir, options) {
|
|
|
859
923
|
|
|
860
924
|
if (platform === 'darwin') {
|
|
861
925
|
// macOS
|
|
862
|
-
|
|
926
|
+
currentTargetDir = '/Library/Application Support/ClaudeCode';
|
|
863
927
|
settingsFile = 'managed-settings.json';
|
|
864
928
|
} else if (platform === 'linux' || (process.platform === 'win32' && process.env.WSL_DISTRO_NAME)) {
|
|
865
929
|
// Linux and WSL
|
|
866
|
-
|
|
930
|
+
currentTargetDir = '/etc/claude-code';
|
|
867
931
|
settingsFile = 'managed-settings.json';
|
|
868
932
|
} else if (platform === 'win32') {
|
|
869
933
|
// Windows
|
|
870
|
-
|
|
934
|
+
currentTargetDir = 'C:\\ProgramData\\ClaudeCode';
|
|
871
935
|
settingsFile = 'managed-settings.json';
|
|
872
936
|
} else {
|
|
873
937
|
console.log(chalk.yellow('⚠️ Platform not supported for enterprise settings. Using user settings instead.'));
|
|
874
938
|
const os = require('os');
|
|
875
|
-
|
|
939
|
+
currentTargetDir = os.homedir();
|
|
876
940
|
settingsFile = 'settings.json';
|
|
877
941
|
}
|
|
878
942
|
|
|
879
943
|
console.log(chalk.yellow(`⚠️ Enterprise settings require administrator privileges.`));
|
|
880
|
-
console.log(chalk.gray(`📍 Target path: ${path.join(
|
|
944
|
+
console.log(chalk.gray(`📍 Target path: ${path.join(currentTargetDir, settingsFile)}`));
|
|
881
945
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
946
|
+
|
|
947
|
+
// Determine target directory and file based on selection
|
|
948
|
+
const claudeDir = path.join(currentTargetDir, '.claude');
|
|
949
|
+
const targetSettingsFile = path.join(claudeDir, settingsFile);
|
|
950
|
+
let existingConfig = {};
|
|
951
|
+
|
|
952
|
+
// For enterprise settings, create directory structure directly (not under .claude)
|
|
953
|
+
if (settingsFile === 'managed-settings.json') {
|
|
954
|
+
// Ensure enterprise directory exists (requires admin privileges)
|
|
955
|
+
try {
|
|
956
|
+
await fs.ensureDir(currentTargetDir);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.log(chalk.red(`❌ Failed to create enterprise directory: ${error.message}`));
|
|
959
|
+
console.log(chalk.yellow('💡 Try running with administrator privileges or choose a different installation location.'));
|
|
960
|
+
continue; // Skip this location and continue with others
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
// Ensure .claude directory exists for regular settings
|
|
964
|
+
await fs.ensureDir(claudeDir);
|
|
898
965
|
}
|
|
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
966
|
|
|
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}`)));
|
|
967
|
+
// Read existing configuration
|
|
968
|
+
const actualTargetFile = settingsFile === 'managed-settings.json'
|
|
969
|
+
? path.join(currentTargetDir, settingsFile)
|
|
970
|
+
: targetSettingsFile;
|
|
971
|
+
|
|
972
|
+
if (await fs.pathExists(actualTargetFile)) {
|
|
973
|
+
existingConfig = await fs.readJson(actualTargetFile);
|
|
974
|
+
console.log(chalk.yellow(`📝 Existing ${settingsFile} found, merging hook configurations...`));
|
|
975
|
+
}
|
|
925
976
|
|
|
926
|
-
|
|
927
|
-
const
|
|
928
|
-
type: 'confirm',
|
|
929
|
-
name: 'shouldOverwrite',
|
|
930
|
-
message: 'Do you want to overwrite the existing hook configuration?',
|
|
931
|
-
default: false
|
|
932
|
-
}]);
|
|
977
|
+
// Check for conflicts before merging (simplified for new array format)
|
|
978
|
+
const conflicts = [];
|
|
933
979
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
980
|
+
// For the new array format, we'll allow appending rather than conflict detection
|
|
981
|
+
// This is because Claude Code's array format naturally supports multiple hooks
|
|
982
|
+
// Conflicts are less likely and generally hooks can coexist
|
|
983
|
+
|
|
984
|
+
// Ask user about conflicts if any exist
|
|
985
|
+
if (conflicts.length > 0) {
|
|
986
|
+
console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing hook "${hookName}" in ${installLocation}:`));
|
|
987
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
|
|
988
|
+
|
|
989
|
+
const inquirer = require('inquirer');
|
|
990
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
991
|
+
type: 'confirm',
|
|
992
|
+
name: 'shouldOverwrite',
|
|
993
|
+
message: `Do you want to overwrite the existing hook configuration in ${installLocation}?`,
|
|
994
|
+
default: false
|
|
995
|
+
}]);
|
|
996
|
+
|
|
997
|
+
if (!shouldOverwrite) {
|
|
998
|
+
console.log(chalk.yellow(`⏹️ Installation of hook "${hookName}" in ${installLocation} cancelled by user.`));
|
|
999
|
+
continue; // Skip this location and continue with others
|
|
1000
|
+
}
|
|
937
1001
|
}
|
|
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]);
|
|
1002
|
+
|
|
1003
|
+
// Deep merge configurations with proper hook array structure
|
|
1004
|
+
const mergedConfig = {
|
|
1005
|
+
...existingConfig
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// Initialize hooks structure if it doesn't exist
|
|
1009
|
+
if (!mergedConfig.hooks) {
|
|
1010
|
+
mergedConfig.hooks = {};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Merge hook configurations properly (Claude Code expects arrays)
|
|
1014
|
+
if (hookConfig.hooks) {
|
|
1015
|
+
Object.keys(hookConfig.hooks).forEach(hookType => {
|
|
1016
|
+
if (!mergedConfig.hooks[hookType]) {
|
|
1017
|
+
// If hook type doesn't exist, just copy the array
|
|
1018
|
+
mergedConfig.hooks[hookType] = hookConfig.hooks[hookType];
|
|
970
1019
|
} else {
|
|
971
|
-
//
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
mergedConfig.hooks[hookType]
|
|
1020
|
+
// If hook type exists, append to the array (Claude Code format)
|
|
1021
|
+
if (Array.isArray(hookConfig.hooks[hookType])) {
|
|
1022
|
+
// New format: array of hook objects
|
|
1023
|
+
if (!Array.isArray(mergedConfig.hooks[hookType])) {
|
|
1024
|
+
// Convert old format to new format
|
|
1025
|
+
mergedConfig.hooks[hookType] = [];
|
|
1026
|
+
}
|
|
1027
|
+
// Append new hooks to existing array
|
|
1028
|
+
mergedConfig.hooks[hookType] = mergedConfig.hooks[hookType].concat(hookConfig.hooks[hookType]);
|
|
1029
|
+
} else {
|
|
1030
|
+
// Old format compatibility: convert to new format
|
|
1031
|
+
console.log(chalk.yellow(`⚠️ Converting old hook format to new Claude Code format for ${hookType}`));
|
|
1032
|
+
if (!Array.isArray(mergedConfig.hooks[hookType])) {
|
|
1033
|
+
mergedConfig.hooks[hookType] = [];
|
|
1034
|
+
}
|
|
1035
|
+
// Add old format hook as a single matcher
|
|
1036
|
+
mergedConfig.hooks[hookType].push({
|
|
1037
|
+
matcher: "*",
|
|
1038
|
+
hooks: [{
|
|
1039
|
+
type: "command",
|
|
1040
|
+
command: hookConfig.hooks[hookType]
|
|
1041
|
+
}]
|
|
1042
|
+
});
|
|
975
1043
|
}
|
|
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
1044
|
}
|
|
985
|
-
}
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Write the merged configuration
|
|
1049
|
+
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
1050
|
+
|
|
1051
|
+
if (!options.silent) {
|
|
1052
|
+
console.log(chalk.green(`✅ Hook "${hookName}" installed successfully in ${installLocation}!`));
|
|
1053
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${actualTargetFile}`));
|
|
1054
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Track successful hook installation for this location
|
|
1058
|
+
trackingService.trackDownload('hook', hookName, {
|
|
1059
|
+
installation_type: 'individual_hook',
|
|
1060
|
+
installation_location: installLocation,
|
|
1061
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
1062
|
+
source: 'github_main'
|
|
986
1063
|
});
|
|
1064
|
+
|
|
1065
|
+
// Increment successful installations counter
|
|
1066
|
+
successfulInstallations++;
|
|
987
1067
|
}
|
|
988
1068
|
|
|
989
|
-
//
|
|
990
|
-
await fs.writeJson(actualTargetFile, mergedConfig, { spaces: 2 });
|
|
991
|
-
|
|
1069
|
+
// Summary after all installations
|
|
992
1070
|
if (!options.silent) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1071
|
+
if (successfulInstallations === installLocations.length) {
|
|
1072
|
+
console.log(chalk.green(`\n🎉 Hook "${hookName}" successfully installed in ${successfulInstallations} location(s)!`));
|
|
1073
|
+
} else {
|
|
1074
|
+
console.log(chalk.yellow(`\n⚠️ Hook "${hookName}" installed in ${successfulInstallations} of ${installLocations.length} location(s).`));
|
|
1075
|
+
const failedCount = installLocations.length - successfulInstallations;
|
|
1076
|
+
console.log(chalk.red(`❌ ${failedCount} installation(s) failed due to permission or other errors.`));
|
|
1077
|
+
}
|
|
996
1078
|
}
|
|
997
1079
|
|
|
998
|
-
|
|
999
|
-
trackingService.trackDownload('hook', hookName, {
|
|
1000
|
-
installation_type: 'individual_hook',
|
|
1001
|
-
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
1002
|
-
source: 'github_main'
|
|
1003
|
-
});
|
|
1080
|
+
return successfulInstallations;
|
|
1004
1081
|
|
|
1005
1082
|
} catch (error) {
|
|
1006
1083
|
console.log(chalk.red(`❌ Error installing hook: ${error.message}`));
|
|
1084
|
+
return 0;
|
|
1007
1085
|
}
|
|
1008
1086
|
}
|
|
1009
1087
|
|
|
@@ -1144,34 +1222,92 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1144
1222
|
console.log(chalk.gray(` Settings: ${components.settings.length}`));
|
|
1145
1223
|
console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
|
|
1146
1224
|
|
|
1225
|
+
// Counter for successfully installed components
|
|
1226
|
+
let successfullyInstalled = 0;
|
|
1227
|
+
|
|
1228
|
+
// Ask for installation locations once for configuration components (if any exist and not in silent mode)
|
|
1229
|
+
let sharedInstallLocations = ['local']; // default
|
|
1230
|
+
const hasSettingsOrHooks = components.settings.length > 0 || components.hooks.length > 0;
|
|
1231
|
+
|
|
1232
|
+
if (hasSettingsOrHooks && !options.yes) {
|
|
1233
|
+
console.log(chalk.blue('\n📍 Choose installation locations for configuration components:'));
|
|
1234
|
+
const inquirer = require('inquirer');
|
|
1235
|
+
const { selectedLocations } = await inquirer.prompt([{
|
|
1236
|
+
type: 'checkbox',
|
|
1237
|
+
name: 'selectedLocations',
|
|
1238
|
+
message: 'Where would you like to install the configuration components? (Select one or more)',
|
|
1239
|
+
choices: [
|
|
1240
|
+
{
|
|
1241
|
+
name: '🏠 User settings (~/.claude/settings.json) - Applies to all projects',
|
|
1242
|
+
value: 'user'
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
name: '📁 Project settings (.claude/settings.json) - Shared with team',
|
|
1246
|
+
value: 'project'
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
name: '⚙️ Local settings (.claude/settings.local.json) - Personal, not committed',
|
|
1250
|
+
value: 'local',
|
|
1251
|
+
checked: true // Default selection
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
name: '🏢 Enterprise managed settings - System-wide policy (requires admin)',
|
|
1255
|
+
value: 'enterprise'
|
|
1256
|
+
}
|
|
1257
|
+
],
|
|
1258
|
+
validate: function(answer) {
|
|
1259
|
+
if (answer.length < 1) {
|
|
1260
|
+
return 'You must choose at least one installation location.';
|
|
1261
|
+
}
|
|
1262
|
+
return true;
|
|
1263
|
+
}
|
|
1264
|
+
}]);
|
|
1265
|
+
|
|
1266
|
+
sharedInstallLocations = selectedLocations;
|
|
1267
|
+
console.log(chalk.cyan(`📋 Will install configuration components in: ${sharedInstallLocations.join(', ')}`));
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1147
1270
|
// Install agents
|
|
1148
1271
|
for (const agent of components.agents) {
|
|
1149
1272
|
console.log(chalk.gray(` Installing agent: ${agent}`));
|
|
1150
|
-
await installIndividualAgent(agent, targetDir, { ...options, silent: true });
|
|
1273
|
+
const agentSuccess = await installIndividualAgent(agent, targetDir, { ...options, silent: true });
|
|
1274
|
+
if (agentSuccess) successfullyInstalled++;
|
|
1151
1275
|
}
|
|
1152
1276
|
|
|
1153
1277
|
// Install commands
|
|
1154
1278
|
for (const command of components.commands) {
|
|
1155
1279
|
console.log(chalk.gray(` Installing command: ${command}`));
|
|
1156
|
-
await installIndividualCommand(command, targetDir, { ...options, silent: true });
|
|
1280
|
+
const commandSuccess = await installIndividualCommand(command, targetDir, { ...options, silent: true });
|
|
1281
|
+
if (commandSuccess) successfullyInstalled++;
|
|
1157
1282
|
}
|
|
1158
1283
|
|
|
1159
1284
|
// Install MCPs
|
|
1160
1285
|
for (const mcp of components.mcps) {
|
|
1161
1286
|
console.log(chalk.gray(` Installing MCP: ${mcp}`));
|
|
1162
|
-
await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
|
|
1287
|
+
const mcpSuccess = await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
|
|
1288
|
+
if (mcpSuccess) successfullyInstalled++;
|
|
1163
1289
|
}
|
|
1164
1290
|
|
|
1165
|
-
// Install settings
|
|
1291
|
+
// Install settings (using shared installation locations)
|
|
1166
1292
|
for (const setting of components.settings) {
|
|
1167
1293
|
console.log(chalk.gray(` Installing setting: ${setting}`));
|
|
1168
|
-
await installIndividualSetting(setting, targetDir, {
|
|
1294
|
+
const settingSuccess = await installIndividualSetting(setting, targetDir, {
|
|
1295
|
+
...options,
|
|
1296
|
+
silent: true,
|
|
1297
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1298
|
+
});
|
|
1299
|
+
if (settingSuccess > 0) successfullyInstalled++;
|
|
1169
1300
|
}
|
|
1170
1301
|
|
|
1171
|
-
// Install hooks
|
|
1302
|
+
// Install hooks (using shared installation locations)
|
|
1172
1303
|
for (const hook of components.hooks) {
|
|
1173
1304
|
console.log(chalk.gray(` Installing hook: ${hook}`));
|
|
1174
|
-
await installIndividualHook(hook, targetDir, {
|
|
1305
|
+
const hookSuccess = await installIndividualHook(hook, targetDir, {
|
|
1306
|
+
...options,
|
|
1307
|
+
silent: true,
|
|
1308
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1309
|
+
});
|
|
1310
|
+
if (hookSuccess > 0) successfullyInstalled++;
|
|
1175
1311
|
}
|
|
1176
1312
|
|
|
1177
1313
|
// Handle YAML workflow if provided
|
|
@@ -1203,7 +1339,15 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1203
1339
|
}
|
|
1204
1340
|
}
|
|
1205
1341
|
|
|
1206
|
-
|
|
1342
|
+
if (successfullyInstalled === totalComponents) {
|
|
1343
|
+
console.log(chalk.green(`\n✅ Successfully installed ${successfullyInstalled} components!`));
|
|
1344
|
+
} else if (successfullyInstalled > 0) {
|
|
1345
|
+
console.log(chalk.yellow(`\n⚠️ Successfully installed ${successfullyInstalled} of ${totalComponents} components.`));
|
|
1346
|
+
console.log(chalk.red(`❌ ${totalComponents - successfullyInstalled} component(s) failed to install.`));
|
|
1347
|
+
} else {
|
|
1348
|
+
console.log(chalk.red(`\n❌ No components were installed successfully.`));
|
|
1349
|
+
return; // Exit early if nothing was installed
|
|
1350
|
+
}
|
|
1207
1351
|
console.log(chalk.cyan(`📁 Components installed to: .claude/`));
|
|
1208
1352
|
|
|
1209
1353
|
if (options.yaml) {
|
|
@@ -1211,17 +1355,7 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1211
1355
|
console.log(chalk.cyan(`🚀 Use the workflow file with Claude Code to execute the complete setup`));
|
|
1212
1356
|
}
|
|
1213
1357
|
|
|
1214
|
-
//
|
|
1215
|
-
trackingService.trackDownload('multi-component', 'batch', {
|
|
1216
|
-
installation_type: 'multi-component',
|
|
1217
|
-
agents_count: components.agents.length,
|
|
1218
|
-
commands_count: components.commands.length,
|
|
1219
|
-
mcps_count: components.mcps.length,
|
|
1220
|
-
settings_count: components.settings.length,
|
|
1221
|
-
hooks_count: components.hooks.length,
|
|
1222
|
-
has_yaml: !!options.yaml,
|
|
1223
|
-
target_directory: path.relative(process.cwd(), targetDir)
|
|
1224
|
-
});
|
|
1358
|
+
// Note: Individual components are already tracked separately in their installation functions
|
|
1225
1359
|
|
|
1226
1360
|
// Handle prompt execution if provided
|
|
1227
1361
|
if (options.prompt) {
|
package/src/tracking-service.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TrackingService -
|
|
2
|
+
* TrackingService - Anonymous download analytics using Supabase database
|
|
3
3
|
* Records component installations for analytics without impacting user experience
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
class TrackingService {
|
|
7
7
|
constructor() {
|
|
8
|
-
this.repoOwner = 'davila7';
|
|
9
|
-
this.repoName = 'claude-code-templates';
|
|
10
8
|
this.trackingEnabled = this.shouldEnableTracking();
|
|
11
9
|
this.timeout = 5000; // 5s timeout for tracking requests
|
|
12
10
|
}
|
|
@@ -83,35 +81,20 @@ class TrackingService {
|
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
/**
|
|
86
|
-
* Send tracking data
|
|
84
|
+
* Send tracking data to database endpoint
|
|
87
85
|
*/
|
|
88
86
|
async sendTrackingData(trackingData) {
|
|
89
87
|
const controller = new AbortController();
|
|
90
88
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
91
89
|
|
|
92
90
|
try {
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
type: trackingData.component_type,
|
|
96
|
-
name: trackingData.component_name,
|
|
97
|
-
platform: trackingData.environment.platform || 'unknown',
|
|
98
|
-
cli: trackingData.environment.cli_version || 'unknown',
|
|
99
|
-
session: trackingData.session_id.substring(0, 8) // Only first 8 chars for privacy
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Use GitHub Pages tracking endpoint via custom domain (no auth needed)
|
|
103
|
-
await fetch(`https://aitmpl.com/api/track.html?${params}`, {
|
|
104
|
-
method: 'GET',
|
|
105
|
-
mode: 'no-cors', // Prevents CORS errors
|
|
106
|
-
signal: controller.signal
|
|
107
|
-
});
|
|
91
|
+
// Send to Vercel database endpoint
|
|
92
|
+
await this.sendToDatabase(trackingData, controller.signal);
|
|
108
93
|
|
|
109
94
|
clearTimeout(timeoutId);
|
|
110
95
|
|
|
111
|
-
// No need to check response with no-cors mode
|
|
112
|
-
// Only show success message when debugging
|
|
113
96
|
if (process.env.CCT_DEBUG === 'true') {
|
|
114
|
-
console.debug('📊 Download tracked successfully
|
|
97
|
+
console.debug('📊 Download tracked successfully');
|
|
115
98
|
}
|
|
116
99
|
|
|
117
100
|
} catch (error) {
|
|
@@ -123,6 +106,63 @@ class TrackingService {
|
|
|
123
106
|
}
|
|
124
107
|
}
|
|
125
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Send tracking data to Vercel database
|
|
111
|
+
*/
|
|
112
|
+
async sendToDatabase(trackingData, signal) {
|
|
113
|
+
try {
|
|
114
|
+
// Extract component path from metadata
|
|
115
|
+
const componentPath = trackingData.metadata?.target_directory ||
|
|
116
|
+
trackingData.metadata?.path ||
|
|
117
|
+
trackingData.component_name;
|
|
118
|
+
|
|
119
|
+
// Extract category from metadata or component name
|
|
120
|
+
const category = trackingData.metadata?.category ||
|
|
121
|
+
(trackingData.component_name.includes('/') ?
|
|
122
|
+
trackingData.component_name.split('/')[0] : 'general');
|
|
123
|
+
|
|
124
|
+
const payload = {
|
|
125
|
+
type: trackingData.component_type,
|
|
126
|
+
name: trackingData.component_name,
|
|
127
|
+
path: componentPath,
|
|
128
|
+
category: category,
|
|
129
|
+
cliVersion: trackingData.environment?.cli_version || 'unknown'
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const response = await fetch('https://www.aitmpl.com/api/track-download-supabase', {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'User-Agent': `claude-code-templates/${trackingData.environment?.cli_version || 'unknown'}`
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify(payload),
|
|
139
|
+
signal: signal
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (process.env.CCT_DEBUG === 'true') {
|
|
143
|
+
console.debug('📊 Payload sent:', JSON.stringify(payload, null, 2));
|
|
144
|
+
if (response.ok) {
|
|
145
|
+
console.debug('📊 Successfully saved to database');
|
|
146
|
+
} else {
|
|
147
|
+
console.debug(`📊 Database save failed with status: ${response.status}`);
|
|
148
|
+
try {
|
|
149
|
+
const errorText = await response.text();
|
|
150
|
+
console.debug('📊 Error response:', errorText);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.debug('📊 Could not read error response');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (process.env.CCT_DEBUG === 'true') {
|
|
159
|
+
console.debug('📊 Database tracking failed:', error.message);
|
|
160
|
+
}
|
|
161
|
+
// Don't throw - tracking should be non-blocking
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
126
166
|
/**
|
|
127
167
|
* Generate a session ID for grouping related downloads
|
|
128
168
|
*/
|