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 +46 -0
- package/bin/create-claude-config.js +5 -1
- package/package.json +2 -2
- package/src/index.js +76 -31
- package/src/sdk/global-agent-manager.js +626 -0
- package/src/tracking-service.js +62 -22
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
|
|
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.
|
|
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": "^
|
|
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
|
-
|
|
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
|
|
721
|
-
if (conflicts.length > 0
|
|
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
|
|
970
|
-
if (conflicts.length > 0
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
+
};
|
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
|
*/
|