claude-code-templates 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,6 +28,7 @@ npx claude-code-templates@latest --health-check
28
28
  - **📊 Real-time Analytics** - Monitor Claude Code sessions with live state detection and performance metrics
29
29
  - **🔍 Health Check** - Comprehensive system validation with actionable recommendations
30
30
  - **🧩 Individual Components** - Install specialized agents, commands, and MCPs individually
31
+ - **🌍 Global Agents** - Create AI agents accessible from anywhere using Claude Code SDK
31
32
 
32
33
  ## 🎯 What You Get
33
34
 
@@ -49,6 +50,51 @@ npx claude-code-templates@latest --health-check
49
50
  | **Go** | Gin, Echo, Fiber | 🚧 Coming Soon |
50
51
  | **Rust** | Axum, Warp, Actix | 🚧 Coming Soon |
51
52
 
53
+ ## 🌍 Global Agents (Claude Code SDK Integration)
54
+
55
+ Create AI agents that can be executed from anywhere using the Claude Code SDK:
56
+
57
+ ```bash
58
+ # Create a global agent (one-time setup)
59
+ npx claude-code-templates@latest --create-agent customer-support
60
+
61
+ # Use the agent from anywhere
62
+ customer-support "Help me with ticket #12345"
63
+ sre-logs "Analyze error patterns in app.log"
64
+ code-reviewer "Review this PR for security issues"
65
+ ```
66
+
67
+ ### Available Global Agents
68
+
69
+ | Agent | Usage | Description |
70
+ |-------|-------|-------------|
71
+ | `customer-support` | `customer-support "query"` | AI customer support specialist |
72
+ | `api-security-audit` | `api-security-audit "analyze endpoints"` | Security auditing for APIs |
73
+ | `react-performance-optimization` | `react-performance-optimization "optimize components"` | React performance expert |
74
+ | `database-optimization` | `database-optimization "improve queries"` | Database performance tuning |
75
+
76
+ ### Global Agent Management
77
+
78
+ ```bash
79
+ # List installed global agents
80
+ npx claude-code-templates@latest --list-agents
81
+
82
+ # Update an agent to latest version
83
+ npx claude-code-templates@latest --update-agent customer-support
84
+
85
+ # Remove an agent
86
+ npx claude-code-templates@latest --remove-agent customer-support
87
+ ```
88
+
89
+ ### How It Works
90
+
91
+ 1. **Download Agent**: Fetches the latest agent from GitHub
92
+ 2. **Generate Executable**: Creates a Node.js script that calls Claude Code SDK
93
+ 3. **Add to PATH**: Makes the agent available globally in your shell
94
+ 4. **Ready to Use**: Execute `agent-name "your prompt"` from any directory
95
+
96
+ The agents use the Claude Code SDK internally to provide specialized AI assistance with domain-specific knowledge and best practices.
97
+
52
98
  ## 📖 Documentation
53
99
 
54
100
  **[📚 Complete Documentation](https://docs.aitmpl.com/)** - Comprehensive guides, examples, and API reference
@@ -41,7 +41,7 @@ console.log(
41
41
 
42
42
  program
43
43
  .name('create-claude-config')
44
- .description('Setup Claude Code configurations for different programming languages')
44
+ .description('Setup Claude Code configurations and create global AI agents powered by Claude Code SDK')
45
45
  .version(require('../package.json').version)
46
46
  .option('-l, --language <language>', 'specify programming language (deprecated, use --template)')
47
47
  .option('-f, --framework <framework>', 'specify framework (deprecated, use --template)')
@@ -66,6 +66,10 @@ program
66
66
  .option('--hook <hook>', 'install specific hook component (supports comma-separated values)')
67
67
  .option('--workflow <workflow>', 'install workflow from hash (#hash) OR workflow YAML (base64 encoded) when used with --agent/--command/--mcp')
68
68
  .option('--prompt <prompt>', 'execute the provided prompt in Claude Code after installation')
69
+ .option('--create-agent <agent>', 'create a global agent accessible from anywhere (e.g., customer-support)')
70
+ .option('--list-agents', 'list all installed global agents')
71
+ .option('--remove-agent <agent>', 'remove a global agent')
72
+ .option('--update-agent <agent>', 'update a global agent to the latest version')
69
73
  .action(async (options) => {
70
74
  try {
71
75
  await createClaudeConfig(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.19.0",
3
+ "version": "1.20.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": {
@@ -90,6 +90,6 @@
90
90
  ],
91
91
  "devDependencies": {
92
92
  "jest": "^30.0.4",
93
- "jest-watch-typeahead": "^2.2.2"
93
+ "jest-watch-typeahead": "^3.0.1"
94
94
  }
95
95
  }
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ const { runAnalytics } = require('./analytics');
16
16
  const { startChatsMobile } = require('./chats-mobile');
17
17
  const { runHealthCheck } = require('./health-check');
18
18
  const { trackingService } = require('./tracking-service');
19
+ const { createGlobalAgent, listGlobalAgents, removeGlobalAgent, updateGlobalAgent } = require('./sdk/global-agent-manager');
19
20
 
20
21
  async function showMainMenu() {
21
22
  console.log('');
@@ -130,6 +131,30 @@ async function createClaudeConfig(options = {}) {
130
131
  return;
131
132
  }
132
133
 
134
+ // Handle global agent creation
135
+ if (options.createAgent) {
136
+ await createGlobalAgent(options.createAgent, options);
137
+ return;
138
+ }
139
+
140
+ // Handle global agent listing
141
+ if (options.listAgents) {
142
+ await listGlobalAgents(options);
143
+ return;
144
+ }
145
+
146
+ // Handle global agent removal
147
+ if (options.removeAgent) {
148
+ await removeGlobalAgent(options.removeAgent, options);
149
+ return;
150
+ }
151
+
152
+ // Handle global agent update
153
+ if (options.updateAgent) {
154
+ await updateGlobalAgent(options.updateAgent, options);
155
+ return;
156
+ }
157
+
133
158
  // Handle command stats analysis (both singular and plural)
134
159
  if (options.commandStats || options.commandsStats) {
135
160
  await runCommandStats(options);
@@ -401,8 +426,11 @@ async function installIndividualAgent(agentName, targetDir, options) {
401
426
  source: 'github_main'
402
427
  });
403
428
 
429
+ return true;
430
+
404
431
  } catch (error) {
405
432
  console.log(chalk.red(`❌ Error installing agent: ${error.message}`));
433
+ return false;
406
434
  }
407
435
  }
408
436
 
@@ -464,8 +492,11 @@ async function installIndividualCommand(commandName, targetDir, options) {
464
492
  source: 'github_main'
465
493
  });
466
494
 
495
+ return true;
496
+
467
497
  } catch (error) {
468
498
  console.log(chalk.red(`❌ Error installing command: ${error.message}`));
499
+ return false;
469
500
  }
470
501
  }
471
502
 
@@ -547,8 +578,11 @@ async function installIndividualMCP(mcpName, targetDir, options) {
547
578
  source: 'github_main'
548
579
  });
549
580
 
581
+ return true;
582
+
550
583
  } catch (error) {
551
584
  console.log(chalk.red(`❌ Error installing MCP: ${error.message}`));
585
+ return false;
552
586
  }
553
587
  }
554
588
 
@@ -712,13 +746,20 @@ async function installIndividualSetting(settingName, targetDir, options) {
712
746
  // Check for conflicting top-level settings
713
747
  Object.keys(settingConfig).forEach(key => {
714
748
  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]}")`);
749
+ existingConfig[key] !== undefined && JSON.stringify(existingConfig[key]) !== JSON.stringify(settingConfig[key])) {
750
+
751
+ // For objects, just indicate the setting name without showing the complex values
752
+ if (typeof existingConfig[key] === 'object' && existingConfig[key] !== null &&
753
+ typeof settingConfig[key] === 'object' && settingConfig[key] !== null) {
754
+ conflicts.push(`Setting "${key}" (will be overwritten with new configuration)`);
755
+ } else {
756
+ conflicts.push(`Setting "${key}" (current: "${existingConfig[key]}", new: "${settingConfig[key]}")`);
757
+ }
717
758
  }
718
759
  });
719
760
 
720
- // Ask user about conflicts if any exist and not in silent mode
721
- if (conflicts.length > 0 && !options.silent) {
761
+ // Ask user about conflicts if any exist
762
+ if (conflicts.length > 0) {
722
763
  console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing setting "${settingName}" in ${installLocation}:`));
723
764
  conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
724
765
 
@@ -734,10 +775,6 @@ async function installIndividualSetting(settingName, targetDir, options) {
734
775
  console.log(chalk.yellow(`⏹️ Installation of setting "${settingName}" in ${installLocation} cancelled by user.`));
735
776
  continue; // Skip this location and continue with others
736
777
  }
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
778
  }
742
779
 
743
780
  // Deep merge configurations
@@ -809,8 +846,11 @@ async function installIndividualSetting(settingName, targetDir, options) {
809
846
  }
810
847
  }
811
848
 
849
+ return successfulInstallations;
850
+
812
851
  } catch (error) {
813
852
  console.log(chalk.red(`❌ Error installing setting: ${error.message}`));
853
+ return 0;
814
854
  }
815
855
  }
816
856
 
@@ -966,8 +1006,8 @@ async function installIndividualHook(hookName, targetDir, options) {
966
1006
  // This is because Claude Code's array format naturally supports multiple hooks
967
1007
  // Conflicts are less likely and generally hooks can coexist
968
1008
 
969
- // Ask user about conflicts if any exist and not in silent mode
970
- if (conflicts.length > 0 && !options.silent) {
1009
+ // Ask user about conflicts if any exist
1010
+ if (conflicts.length > 0) {
971
1011
  console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing hook "${hookName}" in ${installLocation}:`));
972
1012
  conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
973
1013
 
@@ -983,10 +1023,6 @@ async function installIndividualHook(hookName, targetDir, options) {
983
1023
  console.log(chalk.yellow(`⏹️ Installation of hook "${hookName}" in ${installLocation} cancelled by user.`));
984
1024
  continue; // Skip this location and continue with others
985
1025
  }
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
1026
  }
991
1027
 
992
1028
  // Deep merge configurations with proper hook array structure
@@ -1066,8 +1102,11 @@ async function installIndividualHook(hookName, targetDir, options) {
1066
1102
  }
1067
1103
  }
1068
1104
 
1105
+ return successfulInstallations;
1106
+
1069
1107
  } catch (error) {
1070
1108
  console.log(chalk.red(`❌ Error installing hook: ${error.message}`));
1109
+ return 0;
1071
1110
  }
1072
1111
  }
1073
1112
 
@@ -1208,6 +1247,9 @@ async function installMultipleComponents(options, targetDir) {
1208
1247
  console.log(chalk.gray(` Settings: ${components.settings.length}`));
1209
1248
  console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
1210
1249
 
1250
+ // Counter for successfully installed components
1251
+ let successfullyInstalled = 0;
1252
+
1211
1253
  // Ask for installation locations once for configuration components (if any exist and not in silent mode)
1212
1254
  let sharedInstallLocations = ['local']; // default
1213
1255
  const hasSettingsOrHooks = components.settings.length > 0 || components.hooks.length > 0;
@@ -1253,39 +1295,44 @@ async function installMultipleComponents(options, targetDir) {
1253
1295
  // Install agents
1254
1296
  for (const agent of components.agents) {
1255
1297
  console.log(chalk.gray(` Installing agent: ${agent}`));
1256
- await installIndividualAgent(agent, targetDir, { ...options, silent: true });
1298
+ const agentSuccess = await installIndividualAgent(agent, targetDir, { ...options, silent: true });
1299
+ if (agentSuccess) successfullyInstalled++;
1257
1300
  }
1258
1301
 
1259
1302
  // Install commands
1260
1303
  for (const command of components.commands) {
1261
1304
  console.log(chalk.gray(` Installing command: ${command}`));
1262
- await installIndividualCommand(command, targetDir, { ...options, silent: true });
1305
+ const commandSuccess = await installIndividualCommand(command, targetDir, { ...options, silent: true });
1306
+ if (commandSuccess) successfullyInstalled++;
1263
1307
  }
1264
1308
 
1265
1309
  // Install MCPs
1266
1310
  for (const mcp of components.mcps) {
1267
1311
  console.log(chalk.gray(` Installing MCP: ${mcp}`));
1268
- await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
1312
+ const mcpSuccess = await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
1313
+ if (mcpSuccess) successfullyInstalled++;
1269
1314
  }
1270
1315
 
1271
1316
  // Install settings (using shared installation locations)
1272
1317
  for (const setting of components.settings) {
1273
1318
  console.log(chalk.gray(` Installing setting: ${setting}`));
1274
- await installIndividualSetting(setting, targetDir, {
1319
+ const settingSuccess = await installIndividualSetting(setting, targetDir, {
1275
1320
  ...options,
1276
1321
  silent: true,
1277
1322
  sharedInstallLocations: sharedInstallLocations
1278
1323
  });
1324
+ if (settingSuccess > 0) successfullyInstalled++;
1279
1325
  }
1280
1326
 
1281
1327
  // Install hooks (using shared installation locations)
1282
1328
  for (const hook of components.hooks) {
1283
1329
  console.log(chalk.gray(` Installing hook: ${hook}`));
1284
- await installIndividualHook(hook, targetDir, {
1330
+ const hookSuccess = await installIndividualHook(hook, targetDir, {
1285
1331
  ...options,
1286
1332
  silent: true,
1287
1333
  sharedInstallLocations: sharedInstallLocations
1288
1334
  });
1335
+ if (hookSuccess > 0) successfullyInstalled++;
1289
1336
  }
1290
1337
 
1291
1338
  // Handle YAML workflow if provided
@@ -1317,7 +1364,15 @@ async function installMultipleComponents(options, targetDir) {
1317
1364
  }
1318
1365
  }
1319
1366
 
1320
- console.log(chalk.green(`\n✅ Successfully installed ${totalComponents} components!`));
1367
+ if (successfullyInstalled === totalComponents) {
1368
+ console.log(chalk.green(`\n✅ Successfully installed ${successfullyInstalled} components!`));
1369
+ } else if (successfullyInstalled > 0) {
1370
+ console.log(chalk.yellow(`\n⚠️ Successfully installed ${successfullyInstalled} of ${totalComponents} components.`));
1371
+ console.log(chalk.red(`❌ ${totalComponents - successfullyInstalled} component(s) failed to install.`));
1372
+ } else {
1373
+ console.log(chalk.red(`\n❌ No components were installed successfully.`));
1374
+ return; // Exit early if nothing was installed
1375
+ }
1321
1376
  console.log(chalk.cyan(`📁 Components installed to: .claude/`));
1322
1377
 
1323
1378
  if (options.yaml) {
@@ -1325,17 +1380,7 @@ async function installMultipleComponents(options, targetDir) {
1325
1380
  console.log(chalk.cyan(`🚀 Use the workflow file with Claude Code to execute the complete setup`));
1326
1381
  }
1327
1382
 
1328
- // Track installation
1329
- trackingService.trackDownload('multi-component', 'batch', {
1330
- installation_type: 'multi-component',
1331
- agents_count: components.agents.length,
1332
- commands_count: components.commands.length,
1333
- mcps_count: components.mcps.length,
1334
- settings_count: components.settings.length,
1335
- hooks_count: components.hooks.length,
1336
- has_yaml: !!options.yaml,
1337
- target_directory: path.relative(process.cwd(), targetDir)
1338
- });
1383
+ // Note: Individual components are already tracked separately in their installation functions
1339
1384
 
1340
1385
  // Handle prompt execution if provided
1341
1386
  if (options.prompt) {
@@ -0,0 +1,626 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+
7
+ // Global agents directory
8
+ const GLOBAL_AGENTS_DIR = path.join(os.homedir(), '.claude-code-templates');
9
+ const AGENTS_DIR = path.join(GLOBAL_AGENTS_DIR, 'agents');
10
+ const LOCAL_BIN_DIR = path.join(GLOBAL_AGENTS_DIR, 'bin');
11
+
12
+ // Try to use system bin directory for immediate availability
13
+ const SYSTEM_BIN_DIR = '/usr/local/bin';
14
+ const isSystemWritable = () => {
15
+ try {
16
+ const testFile = path.join(SYSTEM_BIN_DIR, '.test-write');
17
+ require('fs').writeFileSync(testFile, 'test', 'utf8');
18
+ require('fs').unlinkSync(testFile);
19
+ return true;
20
+ } catch (error) {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ // Choose the best bin directory
26
+ const BIN_DIR = isSystemWritable() ? SYSTEM_BIN_DIR : LOCAL_BIN_DIR;
27
+
28
+ /**
29
+ * Create a global agent that can be executed from anywhere
30
+ */
31
+ async function createGlobalAgent(agentName, options = {}) {
32
+ console.log(chalk.blue(`🤖 Creating global agent: ${agentName}`));
33
+
34
+ try {
35
+ // Ensure directories exist
36
+ await fs.ensureDir(AGENTS_DIR);
37
+ await fs.ensureDir(LOCAL_BIN_DIR); // Always ensure local bin exists for backups
38
+
39
+ if (BIN_DIR === SYSTEM_BIN_DIR) {
40
+ console.log(chalk.green('🌍 Installing to system directory (immediately available)'));
41
+ } else {
42
+ console.log(chalk.yellow('⚠️ Installing to user directory (requires PATH setup)'));
43
+ await fs.ensureDir(BIN_DIR);
44
+ }
45
+
46
+ // Download agent from GitHub
47
+ const spinner = ora('Downloading agent from GitHub...').start();
48
+
49
+ let githubUrl;
50
+ if (agentName.includes('/')) {
51
+ // Category/agent format
52
+ githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${agentName}.md`;
53
+ } else {
54
+ // Direct agent format - try to find it in any category
55
+ githubUrl = await findAgentUrl(agentName);
56
+ if (!githubUrl) {
57
+ spinner.fail(`Agent "${agentName}" not found`);
58
+ await showAvailableAgents();
59
+ return;
60
+ }
61
+ }
62
+
63
+ const response = await fetch(githubUrl);
64
+ if (!response.ok) {
65
+ spinner.fail(`Failed to download agent: HTTP ${response.status}`);
66
+ if (response.status === 404) {
67
+ await showAvailableAgents();
68
+ }
69
+ return;
70
+ }
71
+
72
+ const agentContent = await response.text();
73
+ spinner.succeed('Agent downloaded successfully');
74
+
75
+ // Extract agent name for file/executable naming
76
+ const executableName = agentName.includes('/') ?
77
+ agentName.split('/')[1] : agentName;
78
+
79
+ // Save agent content
80
+ const agentFile = path.join(AGENTS_DIR, `${executableName}.md`);
81
+ await fs.writeFile(agentFile, agentContent, 'utf8');
82
+
83
+ // Generate executable script
84
+ await generateExecutableScript(executableName, agentFile);
85
+
86
+ console.log(chalk.green(`✅ Global agent '${executableName}' created successfully!`));
87
+ console.log(chalk.cyan('📦 Usage:'));
88
+ console.log(chalk.white(` ${executableName} "your prompt here"`));
89
+
90
+ if (BIN_DIR === SYSTEM_BIN_DIR) {
91
+ console.log(chalk.green('🎉 Ready to use immediately! No setup required.'));
92
+ console.log(chalk.gray('💡 Works in scripts, npm tasks, CI/CD, etc.'));
93
+ } else {
94
+ // Add to PATH (first time setup) only for user directory
95
+ await addToPath();
96
+ console.log(chalk.yellow('🔄 Restart your terminal or run:'));
97
+ console.log(chalk.gray(' source ~/.bashrc # for bash'));
98
+ console.log(chalk.gray(' source ~/.zshrc # for zsh'));
99
+ }
100
+
101
+ } catch (error) {
102
+ console.log(chalk.red(`❌ Error creating global agent: ${error.message}`));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * List installed global agents
108
+ */
109
+ async function listGlobalAgents(options = {}) {
110
+ console.log(chalk.blue('📋 Installed Global Agents:'));
111
+
112
+ try {
113
+ // Check both system and local bin directories
114
+ let systemAgents = [];
115
+ if (await fs.pathExists(SYSTEM_BIN_DIR)) {
116
+ const systemFiles = await fs.readdir(SYSTEM_BIN_DIR);
117
+ for (const file of systemFiles) {
118
+ if (!file.startsWith('.') && await fs.pathExists(path.join(AGENTS_DIR, `${file}.md`))) {
119
+ systemAgents.push(file);
120
+ }
121
+ }
122
+ }
123
+
124
+ const localAgents = await fs.pathExists(LOCAL_BIN_DIR) ?
125
+ (await fs.readdir(LOCAL_BIN_DIR)).filter(file => !file.startsWith('.')) : [];
126
+
127
+ const allAgents = [...new Set([...systemAgents, ...localAgents])];
128
+
129
+ if (allAgents.length === 0) {
130
+ console.log(chalk.yellow('⚠️ No global agents installed yet.'));
131
+ console.log(chalk.gray('💡 Create one with: npx claude-code-templates@latest --create-agent <agent-name>'));
132
+ return;
133
+ }
134
+
135
+ console.log(chalk.green(`\n✅ Found ${allAgents.length} global agent(s):\n`));
136
+
137
+ for (const agent of allAgents) {
138
+ // Check which directory has the agent
139
+ const systemPath = path.join(SYSTEM_BIN_DIR, agent);
140
+ const localPath = path.join(LOCAL_BIN_DIR, agent);
141
+
142
+ let agentPath, location;
143
+ if (await fs.pathExists(systemPath)) {
144
+ agentPath = systemPath;
145
+ location = '🌍 system';
146
+ } else {
147
+ agentPath = localPath;
148
+ location = '👤 user';
149
+ }
150
+
151
+ const stats = await fs.stat(agentPath);
152
+ const isExecutable = (stats.mode & parseInt('111', 8)) !== 0;
153
+
154
+ console.log(chalk.cyan(` ${isExecutable ? '✅' : '❌'} ${agent} (${location})`));
155
+ console.log(chalk.gray(` Usage: ${agent} "your prompt"`));
156
+ console.log(chalk.gray(` Created: ${stats.birthtime.toLocaleDateString()}`));
157
+ console.log('');
158
+ }
159
+
160
+ console.log(chalk.blue('🌟 Global Usage:'));
161
+ console.log(chalk.gray(' • Run from any directory: <agent-name> "prompt"'));
162
+ console.log(chalk.gray(' • List agents: npx claude-code-templates@latest --list-agents'));
163
+ console.log(chalk.gray(' • Remove agent: npx claude-code-templates@latest --remove-agent <name>'));
164
+
165
+ } catch (error) {
166
+ console.log(chalk.red(`❌ Error listing agents: ${error.message}`));
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Remove a global agent
172
+ */
173
+ async function removeGlobalAgent(agentName, options = {}) {
174
+ console.log(chalk.blue(`🗑️ Removing global agent: ${agentName}`));
175
+
176
+ try {
177
+ const systemExecutablePath = path.join(SYSTEM_BIN_DIR, agentName);
178
+ const localExecutablePath = path.join(LOCAL_BIN_DIR, agentName);
179
+ const agentPath = path.join(AGENTS_DIR, `${agentName}.md`);
180
+
181
+ let removed = false;
182
+
183
+ // Remove from system directory
184
+ if (await fs.pathExists(systemExecutablePath)) {
185
+ await fs.remove(systemExecutablePath);
186
+ console.log(chalk.green(`✅ Removed system executable: ${agentName}`));
187
+ removed = true;
188
+ }
189
+
190
+ // Remove from local directory
191
+ if (await fs.pathExists(localExecutablePath)) {
192
+ await fs.remove(localExecutablePath);
193
+ console.log(chalk.green(`✅ Removed local executable: ${agentName}`));
194
+ removed = true;
195
+ }
196
+
197
+ // Remove agent file
198
+ if (await fs.pathExists(agentPath)) {
199
+ await fs.remove(agentPath);
200
+ console.log(chalk.green(`✅ Removed agent file: ${agentName}.md`));
201
+ removed = true;
202
+ }
203
+
204
+ if (!removed) {
205
+ console.log(chalk.yellow(`⚠️ Agent '${agentName}' not found.`));
206
+ console.log(chalk.gray('💡 List available agents with: --list-agents'));
207
+ return;
208
+ }
209
+
210
+ console.log(chalk.green(`🎉 Global agent '${agentName}' removed successfully!`));
211
+
212
+ } catch (error) {
213
+ console.log(chalk.red(`❌ Error removing agent: ${error.message}`));
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Update a global agent
219
+ */
220
+ async function updateGlobalAgent(agentName, options = {}) {
221
+ console.log(chalk.blue(`🔄 Updating global agent: ${agentName}`));
222
+
223
+ try {
224
+ const executablePath = path.join(BIN_DIR, agentName);
225
+
226
+ if (!await fs.pathExists(executablePath)) {
227
+ console.log(chalk.yellow(`⚠️ Agent '${agentName}' not found.`));
228
+ console.log(chalk.gray('💡 Create it with: --create-agent <agent-name>'));
229
+ return;
230
+ }
231
+
232
+ // Re-download and recreate
233
+ console.log(chalk.gray('🔄 Re-downloading latest version...'));
234
+ await createGlobalAgent(agentName, { ...options, update: true });
235
+
236
+ } catch (error) {
237
+ console.log(chalk.red(`❌ Error updating agent: ${error.message}`));
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Generate executable script for an agent
243
+ */
244
+ async function generateExecutableScript(agentName, agentFile) {
245
+ const scriptContent = `#!/usr/bin/env node
246
+
247
+ const { execSync } = require('child_process');
248
+ const path = require('path');
249
+ const fs = require('fs');
250
+
251
+ // Check if Claude CLI is available
252
+ function checkClaudeCLI() {
253
+ try {
254
+ execSync('claude --version', { stdio: 'ignore' });
255
+ return true;
256
+ } catch (error) {
257
+ return false;
258
+ }
259
+ }
260
+
261
+ // Read agent system prompt
262
+ const agentPath = '${agentFile}';
263
+ if (!fs.existsSync(agentPath)) {
264
+ console.error('❌ Agent file not found:', agentPath);
265
+ process.exit(1);
266
+ }
267
+
268
+ const systemPrompt = fs.readFileSync(agentPath, 'utf8');
269
+
270
+ // Parse arguments and detect context
271
+ const args = process.argv.slice(2);
272
+ let userInput = '';
273
+ let explicitFiles = [];
274
+ let explicitDirs = [];
275
+ let autoDetect = true;
276
+
277
+ // Parse command line arguments
278
+ for (let i = 0; i < args.length; i++) {
279
+ const arg = args[i];
280
+
281
+ if (arg === '--file' && i + 1 < args.length) {
282
+ explicitFiles.push(args[++i]);
283
+ autoDetect = false; // Disable auto-detect when explicit files provided
284
+ } else if (arg === '--dir' && i + 1 < args.length) {
285
+ explicitDirs.push(args[++i]);
286
+ autoDetect = false;
287
+ } else if (arg === '--no-auto') {
288
+ autoDetect = false;
289
+ } else if (arg === '--help' || arg === '-h') {
290
+ console.log('Usage: ${agentName} [options] "your prompt"');
291
+ console.log('');
292
+ console.log('Context Options:');
293
+ console.log(' [default] Auto-detect project files (smart context)');
294
+ console.log(' --file <path> Include specific file');
295
+ console.log(' --dir <path> Include specific directory');
296
+ console.log(' --no-auto Disable auto-detection');
297
+ console.log('');
298
+ console.log('Examples:');
299
+ console.log(' ${agentName} "review for security issues" # Auto-detect');
300
+ console.log(' ${agentName} --file auth.js "check this file" # Specific file');
301
+ console.log(' ${agentName} --no-auto "general advice" # No context');
302
+ process.exit(0);
303
+ } else if (!arg.startsWith('--')) {
304
+ userInput += arg + ' ';
305
+ }
306
+ }
307
+
308
+ userInput = userInput.trim();
309
+
310
+ if (!userInput) {
311
+ console.error('❌ Please provide a prompt');
312
+ console.error('Usage: ${agentName} [options] "your prompt"');
313
+ process.exit(1);
314
+ }
315
+
316
+ // Auto-detect project context if enabled
317
+ let contextPrompt = '';
318
+ if (autoDetect && explicitFiles.length === 0 && explicitDirs.length === 0) {
319
+ const fs = require('fs');
320
+ const path = require('path');
321
+ const cwd = process.cwd();
322
+
323
+ // Detect project type and relevant files
324
+ let projectType = 'unknown';
325
+ let relevantFiles = [];
326
+
327
+ // Check for common project indicators
328
+ if (fs.existsSync('package.json')) {
329
+ projectType = 'javascript/node';
330
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
331
+
332
+ // Add framework detection
333
+ if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
334
+ projectType = 'react';
335
+ } else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
336
+ projectType = 'vue';
337
+ } else if (packageJson.dependencies?.next || packageJson.devDependencies?.next) {
338
+ projectType = 'nextjs';
339
+ }
340
+
341
+ // Common files to include for JS projects
342
+ const jsFiles = ['src/', 'lib/', 'components/', 'pages/', 'api/', 'routes/'];
343
+ jsFiles.forEach(dir => {
344
+ if (fs.existsSync(dir)) relevantFiles.push(dir);
345
+ });
346
+
347
+ } else if (fs.existsSync('requirements.txt') || fs.existsSync('pyproject.toml')) {
348
+ projectType = 'python';
349
+ relevantFiles = ['*.py', 'src/', 'app/', 'api/'];
350
+
351
+ } else if (fs.existsSync('Cargo.toml')) {
352
+ projectType = 'rust';
353
+ relevantFiles = ['src/', 'Cargo.toml'];
354
+
355
+ } else if (fs.existsSync('go.mod')) {
356
+ projectType = 'go';
357
+ relevantFiles = ['*.go', 'cmd/', 'internal/', 'pkg/'];
358
+ }
359
+
360
+ // Build context prompt
361
+ if (projectType !== 'unknown') {
362
+ contextPrompt = \`
363
+
364
+ 📁 PROJECT CONTEXT:
365
+ - Project type: \${projectType}
366
+ - Working directory: \${path.basename(cwd)}
367
+ - Auto-detected relevant files/folders: \${relevantFiles.join(', ')}
368
+
369
+ Please analyze the \${userInput} in the context of this \${projectType} project. You have access to read any files in the current directory using the Read tool.\`;
370
+ }
371
+ }
372
+
373
+ // Check Claude CLI availability
374
+ if (!checkClaudeCLI()) {
375
+ console.error('❌ Claude CLI not found in PATH');
376
+ console.error('💡 Install Claude CLI: https://claude.ai/code');
377
+ console.error('💡 Or install via npm: npm install -g @anthropic-ai/claude-code');
378
+ process.exit(1);
379
+ }
380
+
381
+ // Escape quotes in system prompt for shell execution
382
+ const escapedSystemPrompt = systemPrompt.replace(/"/g, '\\\\"').replace(/\`/g, '\\\\\`');
383
+
384
+ // Build final prompt with context
385
+ const finalPrompt = userInput + contextPrompt;
386
+ const escapedFinalPrompt = finalPrompt.replace(/"/g, '\\\\"').replace(/\`/g, '\\\\\`');
387
+
388
+ // Build Claude command with SDK
389
+ const claudeCmd = \`claude -p "\${escapedFinalPrompt}" --append-system-prompt "\${escapedSystemPrompt}"\`;
390
+
391
+ // Show loading indicator
392
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
393
+ let currentFrame = 0;
394
+ const agentDisplayName = '${agentName}'.replace(/-/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase());
395
+
396
+ console.error(\`\\n🤖 \${agentDisplayName} is thinking...\`);
397
+ const loader = setInterval(() => {
398
+ process.stderr.write(\`\\r\${frames[currentFrame]} Claude Code is working... \`);
399
+ currentFrame = (currentFrame + 1) % frames.length;
400
+ }, 100);
401
+
402
+ try {
403
+ // Execute Claude with the agent's system prompt
404
+ execSync(claudeCmd, {
405
+ stdio: ['inherit', 'inherit', 'pipe'],
406
+ cwd: process.cwd()
407
+ });
408
+
409
+ // Clear loader
410
+ clearInterval(loader);
411
+ process.stderr.write('\\r✅ Response ready!\\n\\n');
412
+
413
+ } catch (error) {
414
+ clearInterval(loader);
415
+ process.stderr.write('\\r');
416
+ console.error('❌ Error executing Claude:', error.message);
417
+ process.exit(1);
418
+ }
419
+ `;
420
+
421
+ const scriptPath = path.join(BIN_DIR, agentName);
422
+ await fs.writeFile(scriptPath, scriptContent, 'utf8');
423
+
424
+ // Make executable (Unix/Linux/macOS)
425
+ if (process.platform !== 'win32') {
426
+ await fs.chmod(scriptPath, 0o755);
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Add global agents bin directory to PATH
432
+ */
433
+ async function addToPath() {
434
+ const shell = process.env.SHELL || '';
435
+ const isWindows = process.platform === 'win32';
436
+
437
+ if (isWindows) {
438
+ // Windows PATH management
439
+ console.log(chalk.yellow('🪟 Windows detected:'));
440
+ console.log(chalk.gray(`Add this to your PATH: ${BIN_DIR}`));
441
+ console.log(chalk.gray('Or run this in PowerShell as Administrator:'));
442
+ console.log(chalk.white(`[Environment]::SetEnvironmentVariable("Path", $env:Path + ";${BIN_DIR}", "User")`));
443
+ return;
444
+ }
445
+
446
+ // Unix-like systems
447
+ const pathExport = `export PATH="${BIN_DIR}:$PATH"`;
448
+
449
+ // Determine shell config files to update
450
+ const configFiles = [];
451
+
452
+ if (shell.includes('bash') || !shell) {
453
+ configFiles.push(path.join(os.homedir(), '.bashrc'));
454
+ configFiles.push(path.join(os.homedir(), '.bash_profile'));
455
+ }
456
+
457
+ if (shell.includes('zsh')) {
458
+ configFiles.push(path.join(os.homedir(), '.zshrc'));
459
+ }
460
+
461
+ if (shell.includes('fish')) {
462
+ const fishConfigDir = path.join(os.homedir(), '.config', 'fish');
463
+ await fs.ensureDir(fishConfigDir);
464
+ configFiles.push(path.join(fishConfigDir, 'config.fish'));
465
+ }
466
+
467
+ // Add default files if shell not detected
468
+ if (configFiles.length === 0) {
469
+ configFiles.push(path.join(os.homedir(), '.bashrc'));
470
+ configFiles.push(path.join(os.homedir(), '.zshrc'));
471
+ }
472
+
473
+ // Check if PATH is already added
474
+ let alreadyInPath = false;
475
+
476
+ for (const configFile of configFiles) {
477
+ if (await fs.pathExists(configFile)) {
478
+ const content = await fs.readFile(configFile, 'utf8');
479
+ if (content.includes(BIN_DIR)) {
480
+ alreadyInPath = true;
481
+ break;
482
+ }
483
+ }
484
+ }
485
+
486
+ if (alreadyInPath) {
487
+ console.log(chalk.green('✅ PATH already configured'));
488
+ return;
489
+ }
490
+
491
+ // Add to PATH in config files
492
+ console.log(chalk.blue('🔧 Adding to PATH...'));
493
+
494
+ for (const configFile of configFiles) {
495
+ try {
496
+ let content = '';
497
+ if (await fs.pathExists(configFile)) {
498
+ content = await fs.readFile(configFile, 'utf8');
499
+ }
500
+
501
+ // Add PATH export if not already present
502
+ if (!content.includes(BIN_DIR)) {
503
+ const newContent = content + `\n# Claude Code Templates - Global Agents\n${pathExport}\n`;
504
+ await fs.writeFile(configFile, newContent, 'utf8');
505
+ console.log(chalk.green(`✅ Updated ${path.basename(configFile)}`));
506
+ }
507
+ } catch (error) {
508
+ console.log(chalk.yellow(`⚠️ Could not update ${configFile}: ${error.message}`));
509
+ }
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Find agent URL by searching in all categories
515
+ */
516
+ async function findAgentUrl(agentName) {
517
+ try {
518
+ // First try root level
519
+ const rootUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${agentName}.md`;
520
+ const rootResponse = await fetch(rootUrl);
521
+ if (rootResponse.ok) {
522
+ return rootUrl;
523
+ }
524
+
525
+ // Search in categories
526
+ const categoriesResponse = await fetch('https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/agents');
527
+ if (!categoriesResponse.ok) {
528
+ return null;
529
+ }
530
+
531
+ const contents = await categoriesResponse.json();
532
+
533
+ for (const item of contents) {
534
+ if (item.type === 'dir') {
535
+ const categoryUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${item.name}/${agentName}.md`;
536
+ try {
537
+ const categoryResponse = await fetch(categoryUrl);
538
+ if (categoryResponse.ok) {
539
+ return categoryUrl;
540
+ }
541
+ } catch (error) {
542
+ // Continue searching
543
+ }
544
+ }
545
+ }
546
+
547
+ return null;
548
+ } catch (error) {
549
+ return null;
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Show available agents for user selection
555
+ */
556
+ async function showAvailableAgents() {
557
+ console.log(chalk.yellow('\n📋 Available Agents:'));
558
+ console.log(chalk.gray('Use format: category/agent-name or just agent-name\n'));
559
+
560
+ try {
561
+ const response = await fetch('https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/agents');
562
+ if (!response.ok) {
563
+ console.log(chalk.red('❌ Could not fetch available agents from GitHub'));
564
+ return;
565
+ }
566
+
567
+ const contents = await response.json();
568
+ const agents = [];
569
+
570
+ for (const item of contents) {
571
+ if (item.type === 'file' && item.name.endsWith('.md')) {
572
+ agents.push({ name: item.name.replace('.md', ''), category: 'root' });
573
+ } else if (item.type === 'dir') {
574
+ try {
575
+ const categoryResponse = await fetch(`https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/agents/${item.name}`);
576
+ if (categoryResponse.ok) {
577
+ const categoryContents = await categoryResponse.json();
578
+ for (const categoryItem of categoryContents) {
579
+ if (categoryItem.type === 'file' && categoryItem.name.endsWith('.md')) {
580
+ agents.push({
581
+ name: categoryItem.name.replace('.md', ''),
582
+ category: item.name,
583
+ path: `${item.name}/${categoryItem.name.replace('.md', '')}`
584
+ });
585
+ }
586
+ }
587
+ }
588
+ } catch (error) {
589
+ // Skip category on error
590
+ }
591
+ }
592
+ }
593
+
594
+ // Group by category
595
+ const grouped = agents.reduce((acc, agent) => {
596
+ const category = agent.category === 'root' ? '🤖 General' : `📁 ${agent.category}`;
597
+ if (!acc[category]) acc[category] = [];
598
+ acc[category].push(agent);
599
+ return acc;
600
+ }, {});
601
+
602
+ Object.entries(grouped).forEach(([category, categoryAgents]) => {
603
+ console.log(chalk.cyan(category));
604
+ categoryAgents.forEach(agent => {
605
+ const displayName = agent.path || agent.name;
606
+ console.log(chalk.gray(` • ${displayName}`));
607
+ });
608
+ console.log('');
609
+ });
610
+
611
+ console.log(chalk.blue('Examples:'));
612
+ console.log(chalk.gray(' npx claude-code-templates@latest --create-agent api-security-audit'));
613
+ console.log(chalk.gray(' npx claude-code-templates@latest --create-agent deep-research-team/academic-researcher'));
614
+
615
+ } catch (error) {
616
+ console.log(chalk.red('❌ Error fetching agents:', error.message));
617
+ }
618
+ }
619
+
620
+ module.exports = {
621
+ createGlobalAgent,
622
+ listGlobalAgents,
623
+ removeGlobalAgent,
624
+ updateGlobalAgent,
625
+ showAvailableAgents
626
+ };
@@ -1,12 +1,10 @@
1
1
  /**
2
- * TrackingService - Download analytics using GitHub Issues as backend
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 via public telemetry endpoint (like Google Analytics)
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
- // Build query parameters for GET request (like image tracking)
94
- const params = new URLSearchParams({
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 via telemetry');
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
  */