claude-code-templates 1.22.0 → 1.22.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.
@@ -67,6 +67,7 @@ program
67
67
  .option('--mcp <mcp>', 'install specific MCP component (supports comma-separated values)')
68
68
  .option('--setting <setting>', 'install specific setting component (supports comma-separated values)')
69
69
  .option('--hook <hook>', 'install specific hook component (supports comma-separated values)')
70
+ .option('--skill <skill>', 'install specific skill component (supports comma-separated values)')
70
71
  .option('--workflow <workflow>', 'install workflow from hash (#hash) OR workflow YAML (base64 encoded) when used with --agent/--command/--mcp')
71
72
  .option('--prompt <prompt>', 'execute the provided prompt in Claude Code after installation or in sandbox')
72
73
  .option('--create-agent <agent>', 'create a global agent accessible from anywhere (e.g., customer-support)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.22.0",
3
+ "version": "1.22.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": {
@@ -32,7 +32,11 @@
32
32
  "dev:unlink": "npm unlink -g claude-code-templates",
33
33
  "pretest:commands": "npm run dev:link",
34
34
  "analytics:start": "node src/analytics.js",
35
- "analytics:test": "npm run test:analytics"
35
+ "analytics:test": "npm run test:analytics",
36
+ "security-audit": "node src/security-audit.js",
37
+ "security-audit:ci": "node src/security-audit.js --ci",
38
+ "security-audit:verbose": "node src/security-audit.js --verbose",
39
+ "security-audit:json": "node src/security-audit.js --json --output=security-report.json"
36
40
  },
37
41
  "keywords": [
38
42
  "claude",
@@ -67,6 +71,7 @@
67
71
  "express": "^4.18.2",
68
72
  "fs-extra": "^11.1.1",
69
73
  "inquirer": "^8.2.6",
74
+ "js-yaml": "^4.1.0",
70
75
  "open": "^8.4.2",
71
76
  "ora": "^5.4.1",
72
77
  "uuid": "^11.1.0",
@@ -1576,20 +1576,20 @@
1576
1576
  async loadMoreMessages(conversationId, isInitialLoad = false) {
1577
1577
  const chatMessages = document.getElementById('chatMessages');
1578
1578
  if (!chatMessages) return;
1579
-
1579
+
1580
1580
  // Prevent concurrent loading
1581
1581
  if (this.messagesPagination.isLoading || !this.messagesPagination.hasMore) {
1582
1582
  return;
1583
1583
  }
1584
-
1584
+
1585
1585
  // Ensure we're loading for the correct conversation
1586
1586
  if (this.messagesPagination.conversationId !== conversationId) {
1587
1587
  return;
1588
1588
  }
1589
-
1589
+
1590
1590
  try {
1591
1591
  this.messagesPagination.isLoading = true;
1592
-
1592
+
1593
1593
  if (isInitialLoad) {
1594
1594
  // Show loading state for initial load
1595
1595
  chatMessages.innerHTML = `
@@ -1604,16 +1604,16 @@
1604
1604
  // Show loading indicator at top for infinite scroll
1605
1605
  this.showMessagesLoadingIndicator(true);
1606
1606
  }
1607
-
1607
+
1608
1608
  // Fetch paginated messages from the server
1609
1609
  const response = await fetch(`/api/conversations/${conversationId}/messages?page=${this.messagesPagination.currentPage}&limit=${this.messagesPagination.limit}`);
1610
-
1610
+
1611
1611
  if (!response.ok) {
1612
1612
  throw new Error(`HTTP error! status: ${response.status}`);
1613
1613
  }
1614
-
1614
+
1615
1615
  const messagesData = await response.json();
1616
-
1616
+
1617
1617
  if (messagesData && messagesData.messages) {
1618
1618
  // Update pagination state - handle both paginated and non-paginated responses
1619
1619
  if (messagesData.pagination) {
@@ -1625,24 +1625,25 @@
1625
1625
  this.messagesPagination.hasMore = false;
1626
1626
  this.messagesPagination.currentPage = 1;
1627
1627
  }
1628
-
1628
+
1629
1629
  // Get existing messages or initialize
1630
1630
  let existingMessages = this.loadedMessages.get(conversationId) || [];
1631
-
1631
+
1632
1632
  if (isInitialLoad) {
1633
1633
  // For initial load, replace all messages (newest messages first)
1634
1634
  existingMessages = messagesData.messages;
1635
+ // Render all messages for initial load
1636
+ this.renderCachedMessages(existingMessages, false);
1635
1637
  } else {
1636
1638
  // For infinite scroll, prepend older messages to the beginning
1637
1639
  existingMessages = [...messagesData.messages, ...existingMessages];
1640
+ // Render ONLY the new messages (not all accumulated messages)
1641
+ this.renderCachedMessages(messagesData.messages, true);
1638
1642
  }
1639
-
1643
+
1640
1644
  // Cache the combined messages
1641
1645
  this.loadedMessages.set(conversationId, existingMessages);
1642
-
1643
- // Render messages
1644
- this.renderCachedMessages(existingMessages, !isInitialLoad);
1645
-
1646
+
1646
1647
  // Setup scroll listener for infinite scroll (only on initial load)
1647
1648
  if (isInitialLoad) {
1648
1649
  this.setupMessagesScrollListener(conversationId);
@@ -1655,7 +1656,7 @@
1655
1656
  // Update status based on conversation state
1656
1657
  this.analyzeConversationStatus(existingMessages);
1657
1658
  }
1658
-
1659
+
1659
1660
  } else if (isInitialLoad) {
1660
1661
  chatMessages.innerHTML = `
1661
1662
  <div class="no-messages-found">
@@ -338,12 +338,12 @@ class ConsoleBridge extends EventEmitter {
338
338
  if (Math.random() > 0.7) { // Simulate occasional prompts
339
339
  this.handleDetectedPrompt(`Read file
340
340
 
341
- Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don't\\s*ask\\s*again)?", path:
342
- "../../../../../../../.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates", include: "*.jsonl")
341
+ Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don't\\s*ask\\s*again)?", path:
342
+ "../../../../../../../.claude/projects/-Users-username-Projects-project-name", include: "*.jsonl")
343
343
 
344
344
  Do you want to proceed?
345
345
  āÆ 1. Yes
346
- 2. Yes, and add /Users/danipower/.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates as a working directory for this session
346
+ 2. Yes, and add ~/.claude/projects/-Users-username-Projects-project-name as a working directory for this session
347
347
  3. No, and tell Claude what to do differently (esc)`);
348
348
  }
349
349
 
package/src/index.js CHANGED
@@ -129,7 +129,7 @@ async function createClaudeConfig(options = {}) {
129
129
  }
130
130
 
131
131
  // Handle multiple components installation (new approach)
132
- if (options.agent || options.command || options.mcp || options.setting || options.hook) {
132
+ if (options.agent || options.command || options.mcp || options.setting || options.hook || options.skill) {
133
133
  // If --workflow is used with components, treat it as YAML
134
134
  if (options.workflow) {
135
135
  options.yaml = options.workflow;
@@ -1391,6 +1391,104 @@ async function getAvailableAgentsFromGitHub() {
1391
1391
  }
1392
1392
  }
1393
1393
 
1394
+ async function installIndividualSkill(skillName, targetDir, options) {
1395
+ console.log(chalk.blue(`šŸ’” Installing skill: ${skillName}`));
1396
+
1397
+ try {
1398
+ // Skills always use SKILL.md as the main file (Anthropic standard)
1399
+ // Format: skills/skill-name/SKILL.md
1400
+ const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/skills/${skillName}/SKILL.md`;
1401
+
1402
+ console.log(chalk.gray(`šŸ“„ Downloading from GitHub (main branch)...`));
1403
+
1404
+ const response = await fetch(githubUrl);
1405
+ if (!response.ok) {
1406
+ if (response.status === 404) {
1407
+ console.log(chalk.red(`āŒ Skill "${skillName}" not found`));
1408
+ console.log(chalk.yellow('Available skills: pdf-processing, excel-analysis, git-commit-helper, email-composer'));
1409
+ return false;
1410
+ }
1411
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1412
+ }
1413
+
1414
+ const skillContent = await response.text();
1415
+
1416
+ // Check if there are additional files to download (e.g., referenced .md files)
1417
+ const additionalFiles = {};
1418
+
1419
+ // Look for references to additional files like [FORMS.md](FORMS.md)
1420
+ const referencePattern = /\[([A-Z_]+\.md)\]\(\1\)/g;
1421
+ let match;
1422
+ const skillBaseName = skillName;
1423
+
1424
+ while ((match = referencePattern.exec(skillContent)) !== null) {
1425
+ const referencedFile = match[1];
1426
+ const referencedUrl = githubUrl.replace('SKILL.md', referencedFile);
1427
+
1428
+ try {
1429
+ console.log(chalk.gray(`šŸ“„ Downloading referenced file: ${referencedFile}...`));
1430
+ const refResponse = await fetch(referencedUrl);
1431
+ if (refResponse.ok) {
1432
+ const refContent = await refResponse.text();
1433
+ additionalFiles[`.claude/skills/${skillBaseName}/${referencedFile}`] = {
1434
+ content: refContent,
1435
+ executable: false
1436
+ };
1437
+ console.log(chalk.green(`āœ“ Found referenced file: ${referencedFile}`));
1438
+ }
1439
+ } catch (error) {
1440
+ // Referenced file is optional, continue if not found
1441
+ console.log(chalk.gray(` (Referenced file ${referencedFile} not found, continuing...)`));
1442
+ }
1443
+ }
1444
+
1445
+ // Create .claude/skills/skill-name directory (Anthropic standard structure)
1446
+ const skillsDir = path.join(targetDir, '.claude', 'skills');
1447
+ const skillSubDir = path.join(skillsDir, skillBaseName);
1448
+ await fs.ensureDir(skillSubDir);
1449
+
1450
+ // Always use SKILL.md as filename (Anthropic standard)
1451
+ const targetFile = path.join(skillSubDir, 'SKILL.md');
1452
+
1453
+ // Write the main skill file
1454
+ await fs.writeFile(targetFile, skillContent, 'utf8');
1455
+
1456
+ // Write any additional files
1457
+ for (const [filePath, fileData] of Object.entries(additionalFiles)) {
1458
+ const fullPath = path.join(targetDir, filePath);
1459
+ await fs.ensureDir(path.dirname(fullPath));
1460
+ await fs.writeFile(fullPath, fileData.content, 'utf8');
1461
+
1462
+ if (fileData.executable) {
1463
+ await fs.chmod(fullPath, '755');
1464
+ }
1465
+ }
1466
+
1467
+ if (!options.silent) {
1468
+ console.log(chalk.green(`āœ… Skill "${skillName}" installed successfully!`));
1469
+ console.log(chalk.cyan(`šŸ“ Installed to: ${path.relative(targetDir, targetFile)}`));
1470
+ if (Object.keys(additionalFiles).length > 0) {
1471
+ console.log(chalk.cyan(`šŸ“„ Additional files: ${Object.keys(additionalFiles).length}`));
1472
+ }
1473
+ console.log(chalk.cyan(`šŸ“¦ Downloaded from: ${githubUrl}`));
1474
+ }
1475
+
1476
+ // Track successful skill installation
1477
+ trackingService.trackDownload('skill', skillName, {
1478
+ installation_type: 'individual_skill',
1479
+ target_directory: path.relative(process.cwd(), targetDir),
1480
+ source: 'github_main',
1481
+ has_additional_files: Object.keys(additionalFiles).length > 0
1482
+ });
1483
+
1484
+ return true;
1485
+
1486
+ } catch (error) {
1487
+ console.log(chalk.red(`āŒ Error installing skill: ${error.message}`));
1488
+ return false;
1489
+ }
1490
+ }
1491
+
1394
1492
  /**
1395
1493
  * Install multiple components with optional YAML workflow
1396
1494
  */
@@ -1403,7 +1501,8 @@ async function installMultipleComponents(options, targetDir) {
1403
1501
  commands: [],
1404
1502
  mcps: [],
1405
1503
  settings: [],
1406
- hooks: []
1504
+ hooks: [],
1505
+ skills: []
1407
1506
  };
1408
1507
 
1409
1508
  // Parse comma-separated values for each component type
@@ -1431,8 +1530,13 @@ async function installMultipleComponents(options, targetDir) {
1431
1530
  const hooksInput = Array.isArray(options.hook) ? options.hook.join(',') : options.hook;
1432
1531
  components.hooks = hooksInput.split(',').map(h => h.trim()).filter(h => h);
1433
1532
  }
1434
-
1435
- const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length;
1533
+
1534
+ if (options.skill) {
1535
+ const skillsInput = Array.isArray(options.skill) ? options.skill.join(',') : options.skill;
1536
+ components.skills = skillsInput.split(',').map(s => s.trim()).filter(s => s);
1537
+ }
1538
+
1539
+ const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length + components.skills.length;
1436
1540
 
1437
1541
  if (totalComponents === 0) {
1438
1542
  console.log(chalk.yellow('āš ļø No components specified to install.'));
@@ -1445,6 +1549,7 @@ async function installMultipleComponents(options, targetDir) {
1445
1549
  console.log(chalk.gray(` MCPs: ${components.mcps.length}`));
1446
1550
  console.log(chalk.gray(` Settings: ${components.settings.length}`));
1447
1551
  console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
1552
+ console.log(chalk.gray(` Skills: ${components.skills.length}`));
1448
1553
 
1449
1554
  // Counter for successfully installed components
1450
1555
  let successfullyInstalled = 0;
@@ -1526,14 +1631,21 @@ async function installMultipleComponents(options, targetDir) {
1526
1631
  // Install hooks (using shared installation locations)
1527
1632
  for (const hook of components.hooks) {
1528
1633
  console.log(chalk.gray(` Installing hook: ${hook}`));
1529
- const hookSuccess = await installIndividualHook(hook, targetDir, {
1530
- ...options,
1531
- silent: true,
1532
- sharedInstallLocations: sharedInstallLocations
1634
+ const hookSuccess = await installIndividualHook(hook, targetDir, {
1635
+ ...options,
1636
+ silent: true,
1637
+ sharedInstallLocations: sharedInstallLocations
1533
1638
  });
1534
1639
  if (hookSuccess > 0) successfullyInstalled++;
1535
1640
  }
1536
-
1641
+
1642
+ // Install skills
1643
+ for (const skill of components.skills) {
1644
+ console.log(chalk.gray(` Installing skill: ${skill}`));
1645
+ const skillSuccess = await installIndividualSkill(skill, targetDir, { ...options, silent: true });
1646
+ if (skillSuccess) successfullyInstalled++;
1647
+ }
1648
+
1537
1649
  // Handle YAML workflow if provided
1538
1650
  if (options.yaml) {
1539
1651
  console.log(chalk.blue('\nšŸ“„ Processing workflow YAML...'));
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Security Audit CLI - Validates component security
5
+ *
6
+ * Usage:
7
+ * node src/security-audit.js [options]
8
+ * npm run security-audit
9
+ */
10
+
11
+ const ValidationOrchestrator = require('./validation/ValidationOrchestrator');
12
+ const fs = require('fs-extra');
13
+ const path = require('path');
14
+ const chalk = require('chalk');
15
+
16
+ /**
17
+ * Scan directory for component files
18
+ */
19
+ async function scanComponents(directory) {
20
+ const components = [];
21
+ const componentTypes = ['agents', 'commands', 'mcps', 'settings', 'hooks'];
22
+
23
+ for (const type of componentTypes) {
24
+ const typeDir = path.join(directory, type);
25
+
26
+ if (!await fs.pathExists(typeDir)) {
27
+ continue;
28
+ }
29
+
30
+ // Recursively find all .md files
31
+ const files = await findMarkdownFiles(typeDir);
32
+
33
+ for (const file of files) {
34
+ const content = await fs.readFile(file, 'utf8');
35
+ const relativePath = path.relative(process.cwd(), file);
36
+
37
+ components.push({
38
+ content,
39
+ path: relativePath,
40
+ type: type.slice(0, -1) // Remove 's' (agents -> agent)
41
+ });
42
+ }
43
+ }
44
+
45
+ return components;
46
+ }
47
+
48
+ /**
49
+ * Recursively find all markdown files
50
+ */
51
+ async function findMarkdownFiles(dir) {
52
+ const files = [];
53
+ const entries = await fs.readdir(dir, { withFileTypes: true });
54
+
55
+ for (const entry of entries) {
56
+ const fullPath = path.join(dir, entry.name);
57
+
58
+ if (entry.isDirectory()) {
59
+ const subFiles = await findMarkdownFiles(fullPath);
60
+ files.push(...subFiles);
61
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
62
+ files.push(fullPath);
63
+ }
64
+ }
65
+
66
+ return files;
67
+ }
68
+
69
+ /**
70
+ * Main execution
71
+ */
72
+ async function main() {
73
+ const args = process.argv.slice(2);
74
+ const ciMode = args.includes('--ci');
75
+ const verbose = args.includes('--verbose') || args.includes('-v');
76
+ const jsonOutput = args.includes('--json');
77
+ const outputFile = args.find(arg => arg.startsWith('--output='))?.split('=')[1];
78
+
79
+ console.log(chalk.blue('\nšŸ”’ Claude Code Templates - Security Audit\n'));
80
+ console.log(chalk.gray('━'.repeat(60)));
81
+
82
+ // Determine components directory
83
+ // Check if we're running from the cli-tool directory or the root
84
+ let componentsDir = path.join(process.cwd(), 'components');
85
+ if (!await fs.pathExists(componentsDir)) {
86
+ componentsDir = path.join(process.cwd(), 'cli-tool', 'components');
87
+ }
88
+
89
+ if (!await fs.pathExists(componentsDir)) {
90
+ console.error(chalk.red('āŒ Components directory not found:', componentsDir));
91
+ console.error(chalk.gray(' Tried:', path.join(process.cwd(), 'components')));
92
+ console.error(chalk.gray(' Tried:', path.join(process.cwd(), 'cli-tool', 'components')));
93
+ process.exit(1);
94
+ }
95
+
96
+ console.log(chalk.blue('šŸ“ Scanning components directory...'));
97
+ const components = await scanComponents(componentsDir);
98
+ console.log(chalk.gray(` Found ${components.length} components\n`));
99
+
100
+ // Validate all components
101
+ const orchestrator = new ValidationOrchestrator();
102
+
103
+ console.log(chalk.blue('šŸ” Running security validation...\n'));
104
+ const results = await orchestrator.validateComponents(components, {
105
+ strict: ciMode,
106
+ updateRegistry: false
107
+ });
108
+
109
+ // Generate report
110
+ if (jsonOutput) {
111
+ const report = orchestrator.generateJsonReport(results);
112
+
113
+ if (outputFile) {
114
+ await fs.writeFile(outputFile, report);
115
+ console.log(chalk.green(`āœ… JSON report saved to: ${outputFile}`));
116
+ } else {
117
+ console.log(report);
118
+ }
119
+ } else {
120
+ const report = orchestrator.generateReport(results, {
121
+ verbose,
122
+ colors: !ciMode
123
+ });
124
+ console.log(report);
125
+ }
126
+
127
+ // Summary
128
+ console.log(chalk.bold('\nšŸ“Š Validation Summary:'));
129
+ console.log(chalk.gray('━'.repeat(60)));
130
+ console.log(` Total components: ${results.summary.total}`);
131
+ console.log(` ${chalk.green('āœ… Passed')}: ${results.summary.passed}`);
132
+ console.log(` ${chalk.red('āŒ Failed')}: ${results.summary.failed}`);
133
+ console.log(` ${chalk.yellow('āš ļø Warnings')}: ${results.summary.warnings}`);
134
+ console.log(chalk.gray('━'.repeat(60)));
135
+
136
+ // Exit with appropriate code
137
+ if (ciMode && results.summary.failed > 0) {
138
+ console.error(chalk.red('\nāŒ Security audit failed in CI mode\n'));
139
+ process.exit(1);
140
+ }
141
+
142
+ if (results.summary.failed > 0) {
143
+ console.log(chalk.yellow('\nāš ļø Some components failed validation\n'));
144
+ process.exit(0); // Don't fail in non-CI mode
145
+ }
146
+
147
+ console.log(chalk.green('\nāœ… All components passed security validation\n'));
148
+ process.exit(0);
149
+ }
150
+
151
+ // Error handling
152
+ process.on('unhandledRejection', (error) => {
153
+ console.error(chalk.red('\nāŒ Unhandled error:'), error);
154
+ process.exit(1);
155
+ });
156
+
157
+ // Run
158
+ main().catch((error) => {
159
+ console.error(chalk.red('\nāŒ Security audit failed:'), error.message);
160
+ if (process.argv.includes('--verbose')) {
161
+ console.error(error);
162
+ }
163
+ process.exit(1);
164
+ });