claude-code-templates 1.15.0 → 1.16.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 +1 -1
- package/src/file-operations.js +217 -81
- package/src/index.js +9 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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": {
|
package/src/file-operations.js
CHANGED
|
@@ -4,6 +4,152 @@ const chalk = require('chalk');
|
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
const { getHooksForLanguage, filterHooksBySelection, getMCPsForLanguage, filterMCPsBySelection } = require('./hook-scanner');
|
|
6
6
|
|
|
7
|
+
// GitHub configuration for downloading templates
|
|
8
|
+
const GITHUB_CONFIG = {
|
|
9
|
+
owner: 'davila7',
|
|
10
|
+
repo: 'claude-code-templates',
|
|
11
|
+
branch: 'main',
|
|
12
|
+
templatesPath: 'cli-tool/templates'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Cache for downloaded files to avoid repeated downloads
|
|
16
|
+
const downloadCache = new Map();
|
|
17
|
+
|
|
18
|
+
async function downloadFileFromGitHub(filePath) {
|
|
19
|
+
// Check cache first
|
|
20
|
+
if (downloadCache.has(filePath)) {
|
|
21
|
+
return downloadCache.get(filePath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const githubUrl = `https://raw.githubusercontent.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}/${filePath}`;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(githubUrl);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const content = await response.text();
|
|
33
|
+
downloadCache.set(filePath, content);
|
|
34
|
+
return content;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(chalk.red(`❌ Error downloading ${filePath} from GitHub:`), error.message);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function downloadDirectoryFromGitHub(dirPath) {
|
|
42
|
+
// For directories, we need to get the list of files first
|
|
43
|
+
// GitHub API endpoint to get directory contents
|
|
44
|
+
const apiUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/contents/${GITHUB_CONFIG.templatesPath}/${dirPath}?ref=${GITHUB_CONFIG.branch}`;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(apiUrl);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`Failed to get directory listing for ${dirPath}: ${response.status} ${response.statusText}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const items = await response.json();
|
|
53
|
+
const files = {};
|
|
54
|
+
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
if (item.type === 'file') {
|
|
57
|
+
const relativePath = path.relative(GITHUB_CONFIG.templatesPath, item.path);
|
|
58
|
+
const content = await downloadFileFromGitHub(relativePath);
|
|
59
|
+
files[item.name] = content;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return files;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(chalk.red(`❌ Error downloading directory ${dirPath} from GitHub:`), error.message);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper functions for processing downloaded content
|
|
71
|
+
async function processSettingsFileFromContent(settingsContent, destPath, templateConfig) {
|
|
72
|
+
const settings = JSON.parse(settingsContent);
|
|
73
|
+
|
|
74
|
+
// Filter hooks based on selection
|
|
75
|
+
if (templateConfig.selectedHooks && settings.hooks) {
|
|
76
|
+
settings.hooks = filterHooksBySelection(settings.hooks, templateConfig.selectedHooks);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const destDir = path.dirname(destPath);
|
|
80
|
+
await fs.ensureDir(destDir);
|
|
81
|
+
await fs.writeJson(destPath, settings, { spaces: 2 });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function mergeSettingsFileFromContent(settingsContent, destPath, templateConfig) {
|
|
85
|
+
const newSettings = JSON.parse(settingsContent);
|
|
86
|
+
let existingSettings = {};
|
|
87
|
+
|
|
88
|
+
if (await fs.pathExists(destPath)) {
|
|
89
|
+
existingSettings = await fs.readJson(destPath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Filter hooks based on selection
|
|
93
|
+
if (templateConfig.selectedHooks && newSettings.hooks) {
|
|
94
|
+
newSettings.hooks = filterHooksBySelection(newSettings.hooks, templateConfig.selectedHooks);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Merge settings
|
|
98
|
+
const mergedSettings = {
|
|
99
|
+
...existingSettings,
|
|
100
|
+
...newSettings,
|
|
101
|
+
hooks: {
|
|
102
|
+
...existingSettings.hooks,
|
|
103
|
+
...newSettings.hooks
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const destDir = path.dirname(destPath);
|
|
108
|
+
await fs.ensureDir(destDir);
|
|
109
|
+
await fs.writeJson(destPath, mergedSettings, { spaces: 2 });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function processMCPFileFromContent(mcpContent, destPath, templateConfig) {
|
|
113
|
+
const mcpConfig = JSON.parse(mcpContent);
|
|
114
|
+
|
|
115
|
+
// Filter MCPs based on selection
|
|
116
|
+
if (templateConfig.selectedMCPs && mcpConfig.mcpServers) {
|
|
117
|
+
mcpConfig.mcpServers = filterMCPsBySelection(mcpConfig.mcpServers, templateConfig.selectedMCPs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const destDir = path.dirname(destPath);
|
|
121
|
+
await fs.ensureDir(destDir);
|
|
122
|
+
await fs.writeJson(destPath, mcpConfig, { spaces: 2 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function mergeMCPFileFromContent(mcpContent, destPath, templateConfig) {
|
|
126
|
+
const newMcpConfig = JSON.parse(mcpContent);
|
|
127
|
+
let existingMcpConfig = {};
|
|
128
|
+
|
|
129
|
+
if (await fs.pathExists(destPath)) {
|
|
130
|
+
existingMcpConfig = await fs.readJson(destPath);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Filter MCPs based on selection
|
|
134
|
+
if (templateConfig.selectedMCPs && newMcpConfig.mcpServers) {
|
|
135
|
+
newMcpConfig.mcpServers = filterMCPsBySelection(newMcpConfig.mcpServers, templateConfig.selectedMCPs);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Merge MCP configurations
|
|
139
|
+
const mergedMcpConfig = {
|
|
140
|
+
...existingMcpConfig,
|
|
141
|
+
...newMcpConfig,
|
|
142
|
+
mcpServers: {
|
|
143
|
+
...existingMcpConfig.mcpServers,
|
|
144
|
+
...newMcpConfig.mcpServers
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const destDir = path.dirname(destPath);
|
|
149
|
+
await fs.ensureDir(destDir);
|
|
150
|
+
await fs.writeJson(destPath, mergedMcpConfig, { spaces: 2 });
|
|
151
|
+
}
|
|
152
|
+
|
|
7
153
|
async function checkExistingFiles(targetDir, templateConfig) {
|
|
8
154
|
const existingFiles = [];
|
|
9
155
|
|
|
@@ -86,7 +232,7 @@ async function createBackups(existingFiles, targetDir) {
|
|
|
86
232
|
}
|
|
87
233
|
|
|
88
234
|
async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
|
|
89
|
-
|
|
235
|
+
console.log(chalk.gray(`📥 Downloading templates from GitHub (${GITHUB_CONFIG.branch} branch)...`));
|
|
90
236
|
|
|
91
237
|
// Check for existing files and get user preference
|
|
92
238
|
const existingFiles = await checkExistingFiles(targetDir, templateConfig);
|
|
@@ -114,7 +260,6 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
|
|
|
114
260
|
|
|
115
261
|
// Copy base files and framework-specific files
|
|
116
262
|
for (const file of templateConfig.files) {
|
|
117
|
-
const sourcePath = path.join(templateDir, file.source);
|
|
118
263
|
const destPath = path.join(targetDir, file.destination);
|
|
119
264
|
|
|
120
265
|
try {
|
|
@@ -123,94 +268,115 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
|
|
|
123
268
|
// This is a framework-specific commands directory - merge with existing commands
|
|
124
269
|
await fs.ensureDir(destPath);
|
|
125
270
|
|
|
126
|
-
//
|
|
127
|
-
const frameworkFiles = await
|
|
128
|
-
for (const
|
|
129
|
-
const
|
|
130
|
-
const destFile = path.join(destPath, frameworkFile);
|
|
271
|
+
// Download framework-specific commands from GitHub
|
|
272
|
+
const frameworkFiles = await downloadDirectoryFromGitHub(file.source);
|
|
273
|
+
for (const [frameworkFileName, content] of Object.entries(frameworkFiles)) {
|
|
274
|
+
const destFile = path.join(destPath, frameworkFileName);
|
|
131
275
|
|
|
132
276
|
// In merge mode, skip if file already exists
|
|
133
277
|
if (userAction === 'merge' && await fs.pathExists(destFile)) {
|
|
134
|
-
console.log(chalk.blue(`⏭️ Skipped ${
|
|
278
|
+
console.log(chalk.blue(`⏭️ Skipped ${frameworkFileName} (already exists)`));
|
|
135
279
|
continue;
|
|
136
280
|
}
|
|
137
281
|
|
|
138
|
-
await fs.
|
|
282
|
+
await fs.writeFile(destFile, content, 'utf8');
|
|
139
283
|
}
|
|
140
284
|
|
|
141
|
-
console.log(chalk.green(`✓
|
|
285
|
+
console.log(chalk.green(`✓ Downloaded framework commands ${file.source} → ${file.destination}`));
|
|
142
286
|
} else if (file.source.includes('.claude') && !file.source.includes('examples/')) {
|
|
143
|
-
// This is base .claude directory -
|
|
144
|
-
await fs.
|
|
145
|
-
overwrite: shouldOverwrite,
|
|
146
|
-
filter: (src) => {
|
|
147
|
-
// Skip the commands directory itself - we'll handle it separately
|
|
148
|
-
return !src.endsWith('.claude/commands');
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Now handle base commands specifically
|
|
153
|
-
const baseCommandsPath = path.join(sourcePath, 'commands');
|
|
154
|
-
const destCommandsPath = path.join(destPath, 'commands');
|
|
287
|
+
// This is base .claude directory - download it but handle commands specially
|
|
288
|
+
await fs.ensureDir(destPath);
|
|
155
289
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Copy base commands, but exclude framework-specific ones that were moved
|
|
160
|
-
const baseCommands = await fs.readdir(baseCommandsPath);
|
|
161
|
-
const excludeCommands = ['react-component.md', 'route.md', 'api-endpoint.md']; // Commands moved to framework dirs
|
|
290
|
+
// Download base .claude directory structure from GitHub
|
|
291
|
+
try {
|
|
292
|
+
const baseClaudeFiles = await downloadDirectoryFromGitHub(file.source);
|
|
162
293
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const destFile = path.join(
|
|
294
|
+
// Write non-command files first
|
|
295
|
+
for (const [fileName, content] of Object.entries(baseClaudeFiles)) {
|
|
296
|
+
if (fileName !== 'commands') { // Skip commands directory, handle separately
|
|
297
|
+
const destFile = path.join(destPath, fileName);
|
|
167
298
|
|
|
168
299
|
// In merge mode, skip if file already exists
|
|
169
300
|
if (userAction === 'merge' && await fs.pathExists(destFile)) {
|
|
170
|
-
console.log(chalk.blue(`⏭️ Skipped ${
|
|
301
|
+
console.log(chalk.blue(`⏭️ Skipped ${fileName} (already exists)`));
|
|
171
302
|
continue;
|
|
172
303
|
}
|
|
173
304
|
|
|
174
|
-
await fs.
|
|
305
|
+
await fs.writeFile(destFile, content, 'utf8');
|
|
175
306
|
}
|
|
176
307
|
}
|
|
308
|
+
|
|
309
|
+
// Now handle base commands specifically
|
|
310
|
+
const destCommandsPath = path.join(destPath, 'commands');
|
|
311
|
+
await fs.ensureDir(destCommandsPath);
|
|
312
|
+
|
|
313
|
+
// Download base commands from GitHub
|
|
314
|
+
const baseCommandsDir = `${file.source}/commands`;
|
|
315
|
+
try {
|
|
316
|
+
const baseCommands = await downloadDirectoryFromGitHub(baseCommandsDir);
|
|
317
|
+
const excludeCommands = ['react-component.md', 'route.md', 'api-endpoint.md']; // Commands moved to framework dirs
|
|
318
|
+
|
|
319
|
+
for (const [baseCommandName, commandContent] of Object.entries(baseCommands)) {
|
|
320
|
+
if (!excludeCommands.includes(baseCommandName)) {
|
|
321
|
+
const destFile = path.join(destCommandsPath, baseCommandName);
|
|
322
|
+
|
|
323
|
+
// In merge mode, skip if file already exists
|
|
324
|
+
if (userAction === 'merge' && await fs.pathExists(destFile)) {
|
|
325
|
+
console.log(chalk.blue(`⏭️ Skipped ${baseCommandName} (already exists)`));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await fs.writeFile(destFile, commandContent, 'utf8');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch (error) {
|
|
333
|
+
// Commands directory might not exist for some templates, that's ok
|
|
334
|
+
console.log(chalk.yellow(`⚠️ No commands directory found for ${baseCommandsDir}`));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(chalk.red(`❌ Error downloading .claude directory: ${error.message}`));
|
|
339
|
+
throw error;
|
|
177
340
|
}
|
|
178
341
|
|
|
179
|
-
console.log(chalk.green(`✓
|
|
342
|
+
console.log(chalk.green(`✓ Downloaded base configuration and commands ${file.source} → ${file.destination}`));
|
|
180
343
|
} else if (file.source.includes('settings.json') && templateConfig.selectedHooks) {
|
|
344
|
+
// Download and process settings.json with hooks
|
|
345
|
+
const settingsContent = await downloadFileFromGitHub(file.source);
|
|
346
|
+
|
|
181
347
|
// In merge mode, merge settings instead of overwriting
|
|
182
348
|
if (userAction === 'merge') {
|
|
183
|
-
await
|
|
349
|
+
await mergeSettingsFileFromContent(settingsContent, destPath, templateConfig);
|
|
184
350
|
console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected hooks)`));
|
|
185
351
|
} else {
|
|
186
|
-
await
|
|
187
|
-
console.log(chalk.green(`✓
|
|
352
|
+
await processSettingsFileFromContent(settingsContent, destPath, templateConfig);
|
|
353
|
+
console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected hooks)`));
|
|
188
354
|
}
|
|
189
355
|
} else if (file.source.includes('.mcp.json') && templateConfig.selectedMCPs) {
|
|
356
|
+
// Download and process MCP config with selected MCPs
|
|
357
|
+
const mcpContent = await downloadFileFromGitHub(file.source);
|
|
358
|
+
|
|
190
359
|
// In merge mode, merge MCP config instead of overwriting
|
|
191
360
|
if (userAction === 'merge') {
|
|
192
|
-
await
|
|
361
|
+
await mergeMCPFileFromContent(mcpContent, destPath, templateConfig);
|
|
193
362
|
console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected MCPs)`));
|
|
194
363
|
} else {
|
|
195
|
-
await
|
|
196
|
-
console.log(chalk.green(`✓
|
|
364
|
+
await processMCPFileFromContent(mcpContent, destPath, templateConfig);
|
|
365
|
+
console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected MCPs)`));
|
|
197
366
|
}
|
|
198
367
|
} else {
|
|
199
|
-
//
|
|
368
|
+
// Download regular files (CLAUDE.md, etc.)
|
|
200
369
|
// In merge mode, skip if file already exists
|
|
201
370
|
if (userAction === 'merge' && await fs.pathExists(destPath)) {
|
|
202
371
|
console.log(chalk.blue(`⏭️ Skipped ${file.destination} (already exists)`));
|
|
203
372
|
continue;
|
|
204
373
|
}
|
|
205
374
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination}`));
|
|
375
|
+
const fileContent = await downloadFileFromGitHub(file.source);
|
|
376
|
+
const destDir = path.dirname(destPath);
|
|
377
|
+
await fs.ensureDir(destDir);
|
|
378
|
+
await fs.writeFile(destPath, fileContent, 'utf8');
|
|
379
|
+
console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination}`));
|
|
214
380
|
}
|
|
215
381
|
} catch (error) {
|
|
216
382
|
console.error(chalk.red(`✗ Failed to copy ${file.source}:`), error.message);
|
|
@@ -218,38 +384,8 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
|
|
|
218
384
|
}
|
|
219
385
|
}
|
|
220
386
|
|
|
387
|
+
console.log(chalk.cyan(`📦 All templates downloaded from: https://github.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/tree/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}`));
|
|
221
388
|
return true; // Indicate successful completion
|
|
222
|
-
|
|
223
|
-
// Copy selected commands individually
|
|
224
|
-
if (templateConfig.selectedCommands && templateConfig.selectedCommands.length > 0) {
|
|
225
|
-
const commandsDir = path.join(targetDir, '.claude', 'commands');
|
|
226
|
-
await fs.ensureDir(commandsDir);
|
|
227
|
-
|
|
228
|
-
for (const command of templateConfig.selectedCommands) {
|
|
229
|
-
try {
|
|
230
|
-
const commandFileName = `${command.name}.md`;
|
|
231
|
-
const destPath = path.join(commandsDir, commandFileName);
|
|
232
|
-
|
|
233
|
-
await fs.copy(command.filePath, destPath);
|
|
234
|
-
console.log(chalk.green(`✓ Added command: ${command.displayName}`));
|
|
235
|
-
} catch (error) {
|
|
236
|
-
console.error(chalk.red(`✗ Failed to copy command ${command.name}:`), error.message);
|
|
237
|
-
// Don't throw - continue with other commands
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
console.log(chalk.cyan(`📋 Installed ${templateConfig.selectedCommands.length} commands`));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Report hook selection
|
|
245
|
-
if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
|
|
246
|
-
console.log(chalk.magenta(`🔧 Installed ${templateConfig.selectedHooks.length} automation hooks`));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Report MCP selection
|
|
250
|
-
if (templateConfig.selectedMCPs && templateConfig.selectedMCPs.length > 0) {
|
|
251
|
-
console.log(chalk.blue(`🔧 Installed ${templateConfig.selectedMCPs.length} MCP`));
|
|
252
|
-
}
|
|
253
389
|
}
|
|
254
390
|
|
|
255
391
|
async function runPostInstallationValidation(targetDir, templateConfig) {
|
package/src/index.js
CHANGED
|
@@ -321,35 +321,22 @@ async function installIndividualAgent(agentName, targetDir, options) {
|
|
|
321
321
|
|
|
322
322
|
const agentContent = await response.text();
|
|
323
323
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const framework = extractFrameworkFromAgent(agentContent, agentName);
|
|
324
|
+
// Create .claude/agents directory if it doesn't exist
|
|
325
|
+
const agentsDir = path.join(targetDir, '.claude', 'agents');
|
|
326
|
+
await fs.ensureDir(agentsDir);
|
|
328
327
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// Install the template that contains this agent (avoid recursion)
|
|
333
|
-
const setupOptions = {
|
|
334
|
-
...options,
|
|
335
|
-
language,
|
|
336
|
-
framework,
|
|
337
|
-
yes: true,
|
|
338
|
-
targetDirectory: targetDir,
|
|
339
|
-
agent: null // Remove agent to avoid recursion
|
|
340
|
-
};
|
|
341
|
-
delete setupOptions.agent;
|
|
342
|
-
await createClaudeConfig(setupOptions);
|
|
328
|
+
// Write the agent file
|
|
329
|
+
const targetFile = path.join(agentsDir, `${agentName}.md`);
|
|
330
|
+
await fs.writeFile(targetFile, agentContent, 'utf8');
|
|
343
331
|
|
|
344
332
|
console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
|
|
333
|
+
console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
345
334
|
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
346
335
|
|
|
347
336
|
// Track successful agent installation
|
|
348
337
|
trackingService.trackDownload('agent', agentName, {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
installation_type: 'individual_agent',
|
|
352
|
-
template_installed: true,
|
|
338
|
+
installation_type: 'individual_component',
|
|
339
|
+
target_directory: path.relative(process.cwd(), targetDir),
|
|
353
340
|
source: 'github_main'
|
|
354
341
|
});
|
|
355
342
|
|