claude-code-templates 1.22.0 → 1.22.2

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
@@ -38,6 +38,7 @@ npx claude-code-templates@latest --health-check
38
38
  | **Commands** | Custom slash commands for development tasks | `/generate-tests`, `/check-file`, `/optimize-bundle` |
39
39
  | **Agents** | AI specialists for specific domains | API security audit, React performance, database optimization |
40
40
  | **MCPs** | External service integrations | GitHub, databases, development tools |
41
+ | **Skills** | Modular capabilities with progressive disclosure | PDF processing, algorithmic art, MCP builder |
41
42
  | **Analytics** | Real-time monitoring dashboard | Live session tracking, usage statistics, exports |
42
43
 
43
44
  ## šŸ› ļø Supported Technologies
@@ -95,6 +96,61 @@ npx claude-code-templates@latest --remove-agent customer-support
95
96
 
96
97
  The agents use the Claude Code SDK internally to provide specialized AI assistance with domain-specific knowledge and best practices.
97
98
 
99
+ ## šŸŽØ Skills (Anthropic Format)
100
+
101
+ Install modular capabilities that Claude loads dynamically using Anthropic's progressive disclosure pattern:
102
+
103
+ ```bash
104
+ # Install individual skills
105
+ npx claude-code-templates@latest --skill pdf-processing-pro
106
+ npx claude-code-templates@latest --skill algorithmic-art
107
+ npx claude-code-templates@latest --skill mcp-builder
108
+
109
+ # Install multiple skills
110
+ npx claude-code-templates@latest --skill pdf-anthropic,docx,xlsx,pptx
111
+ ```
112
+
113
+ ### Featured Skills
114
+
115
+ #### šŸŽØ Creative & Design
116
+ - **algorithmic-art** - Create generative art using p5.js with seeded randomness
117
+ - **canvas-design** - Design beautiful visual art in .png and .pdf formats
118
+ - **slack-gif-creator** - Create animated GIFs optimized for Slack
119
+
120
+ #### šŸ’» Development & Technical
121
+ - **mcp-builder** - Guide for creating high-quality MCP servers
122
+ - **artifacts-builder** - Build complex HTML artifacts with React and Tailwind
123
+ - **webapp-testing** - Test local web applications using Playwright
124
+ - **skill-creator** - Guide for creating effective skills
125
+
126
+ #### šŸ“„ Document Processing
127
+ - **pdf-processing-pro** - Production-ready PDF toolkit (forms, tables, OCR)
128
+ - **pdf-anthropic** - Anthropic's comprehensive PDF manipulation toolkit
129
+ - **docx** - Create, edit, and analyze Word documents
130
+ - **xlsx** - Create, edit, and analyze Excel spreadsheets
131
+ - **pptx** - Create, edit, and analyze PowerPoint presentations
132
+
133
+ #### šŸ¢ Enterprise & Communication
134
+ - **brand-guidelines** - Apply Anthropic's official brand guidelines
135
+ - **internal-comms** - Write internal communications (reports, newsletters, FAQs)
136
+ - **theme-factory** - Style artifacts with professional themes
137
+
138
+ ### Skills Architecture
139
+
140
+ Skills follow Anthropic's progressive disclosure pattern:
141
+ - **Metadata** - Always loaded (name, description)
142
+ - **Instructions** - Loaded when skill is triggered
143
+ - **Resources** - Reference files loaded only when needed
144
+ - **Scripts** - Execute without loading code into context
145
+
146
+ ### Attribution
147
+
148
+ Skills from [anthropics/skills](https://github.com/anthropics/skills):
149
+ - **Open Source** (Apache 2.0): algorithmic-art, mcp-builder, skill-creator, artifacts-builder, and more
150
+ - **Source-Available** (Reference): docx, pdf-anthropic, pptx, xlsx
151
+
152
+ See [ANTHROPIC_ATTRIBUTION.md](cli-tool/components/skills/ANTHROPIC_ATTRIBUTION.md) for complete license information.
153
+
98
154
  ## šŸ“– Documentation
99
155
 
100
156
  **[šŸ“š Complete Documentation](https://docs.aitmpl.com/)** - Comprehensive guides, examples, and API reference
@@ -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.2",
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,166 @@ 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 can be in format: "skill-name" or "category/skill-name"
1399
+ // Extract the actual skill name (last part of the path)
1400
+ const skillBaseName = skillName.includes('/') ? skillName.split('/').pop() : skillName;
1401
+
1402
+ // Skills always use SKILL.md as the main file (Anthropic standard)
1403
+ // Format: skills/category/skill-name/SKILL.md
1404
+ const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/skills/${skillName}/SKILL.md`;
1405
+
1406
+ console.log(chalk.gray(`šŸ“„ Downloading from GitHub (main branch)...`));
1407
+
1408
+ const response = await fetch(githubUrl);
1409
+ if (!response.ok) {
1410
+ if (response.status === 404) {
1411
+ console.log(chalk.red(`āŒ Skill "${skillName}" not found`));
1412
+ console.log(chalk.yellow('šŸ’” Tip: Use format "category/skill-name" (e.g., creative-design/algorithmic-art)'));
1413
+ console.log(chalk.yellow('Available categories: creative-design, development, document-processing, enterprise-communication'));
1414
+ return false;
1415
+ }
1416
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1417
+ }
1418
+
1419
+ const skillContent = await response.text();
1420
+
1421
+ // Check if there are additional files to download (e.g., referenced .md files, scripts, reference)
1422
+ const additionalFiles = {};
1423
+
1424
+ // 1. Look for references to additional files like [FORMS.md](FORMS.md) or [reference](./reference/)
1425
+ const referencePattern = /\[([A-Z_]+\.md)\]\(\1\)|\[.*?\]\(\.\/([a-z_]+)\/\)/g;
1426
+ let match;
1427
+
1428
+ while ((match = referencePattern.exec(skillContent)) !== null) {
1429
+ const referencedFile = match[1] || match[2];
1430
+ const referencedUrl = githubUrl.replace('SKILL.md', referencedFile);
1431
+
1432
+ try {
1433
+ console.log(chalk.gray(`šŸ“„ Downloading referenced file: ${referencedFile}...`));
1434
+ const refResponse = await fetch(referencedUrl);
1435
+ if (refResponse.ok) {
1436
+ const refContent = await refResponse.text();
1437
+ additionalFiles[`.claude/skills/${skillBaseName}/${referencedFile}`] = {
1438
+ content: refContent,
1439
+ executable: false
1440
+ };
1441
+ console.log(chalk.green(`āœ“ Found referenced file: ${referencedFile}`));
1442
+ }
1443
+ } catch (error) {
1444
+ // Referenced file is optional, continue if not found
1445
+ console.log(chalk.gray(` (Referenced file ${referencedFile} not found, continuing...)`));
1446
+ }
1447
+ }
1448
+
1449
+ // 2. Try to download common directories (scripts/, reference/) from GitHub API
1450
+ const githubApiUrl = `https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/skills/${skillName}`;
1451
+
1452
+ try {
1453
+ const dirResponse = await fetch(githubApiUrl, {
1454
+ headers: {
1455
+ 'Accept': 'application/vnd.github.v3+json',
1456
+ 'User-Agent': 'claude-code-templates'
1457
+ }
1458
+ });
1459
+
1460
+ if (dirResponse.ok) {
1461
+ const dirContents = await dirResponse.json();
1462
+
1463
+ // Find directories (scripts, reference, templates)
1464
+ for (const item of dirContents) {
1465
+ if (item.type === 'dir' && ['scripts', 'reference', 'templates'].includes(item.name)) {
1466
+ console.log(chalk.gray(`šŸ“‚ Found directory: ${item.name}/`));
1467
+
1468
+ // Fetch directory contents recursively
1469
+ const subdirResponse = await fetch(item.url, {
1470
+ headers: {
1471
+ 'Accept': 'application/vnd.github.v3+json',
1472
+ 'User-Agent': 'claude-code-templates'
1473
+ }
1474
+ });
1475
+
1476
+ if (subdirResponse.ok) {
1477
+ const subdirContents = await subdirResponse.json();
1478
+
1479
+ for (const file of subdirContents) {
1480
+ if (file.type === 'file') {
1481
+ try {
1482
+ const fileResponse = await fetch(file.download_url);
1483
+ if (fileResponse.ok) {
1484
+ const fileContent = await fileResponse.text();
1485
+ const isExecutable = file.name.endsWith('.py') || file.name.endsWith('.sh');
1486
+
1487
+ additionalFiles[`.claude/skills/${skillBaseName}/${item.name}/${file.name}`] = {
1488
+ content: fileContent,
1489
+ executable: isExecutable
1490
+ };
1491
+ console.log(chalk.green(`āœ“ Downloaded: ${item.name}/${file.name}`));
1492
+ }
1493
+ } catch (err) {
1494
+ console.log(chalk.gray(` (Could not download ${item.name}/${file.name})`));
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ }
1500
+ }
1501
+ }
1502
+ } catch (error) {
1503
+ // GitHub API access optional, continue if not available
1504
+ console.log(chalk.gray(` (Could not access GitHub API for directory listing)`));
1505
+ }
1506
+
1507
+ // Create .claude/skills/skill-name directory (Anthropic standard structure)
1508
+ const skillsDir = path.join(targetDir, '.claude', 'skills');
1509
+ const skillSubDir = path.join(skillsDir, skillBaseName);
1510
+ await fs.ensureDir(skillSubDir);
1511
+
1512
+ // Always use SKILL.md as filename (Anthropic standard)
1513
+ const targetFile = path.join(skillSubDir, 'SKILL.md');
1514
+
1515
+ // Write the main skill file
1516
+ await fs.writeFile(targetFile, skillContent, 'utf8');
1517
+
1518
+ // Write any additional files
1519
+ for (const [filePath, fileData] of Object.entries(additionalFiles)) {
1520
+ const fullPath = path.join(targetDir, filePath);
1521
+ await fs.ensureDir(path.dirname(fullPath));
1522
+ await fs.writeFile(fullPath, fileData.content, 'utf8');
1523
+
1524
+ if (fileData.executable) {
1525
+ await fs.chmod(fullPath, '755');
1526
+ }
1527
+ }
1528
+
1529
+ if (!options.silent) {
1530
+ console.log(chalk.green(`āœ… Skill "${skillName}" installed successfully!`));
1531
+ console.log(chalk.cyan(`šŸ“ Installed to: ${path.relative(targetDir, targetFile)}`));
1532
+ if (Object.keys(additionalFiles).length > 0) {
1533
+ console.log(chalk.cyan(`šŸ“„ Additional files: ${Object.keys(additionalFiles).length}`));
1534
+ }
1535
+ console.log(chalk.cyan(`šŸ“¦ Downloaded from: ${githubUrl}`));
1536
+ }
1537
+
1538
+ // Track successful skill installation
1539
+ trackingService.trackDownload('skill', skillName, {
1540
+ installation_type: 'individual_skill',
1541
+ target_directory: path.relative(process.cwd(), targetDir),
1542
+ source: 'github_main',
1543
+ has_additional_files: Object.keys(additionalFiles).length > 0
1544
+ });
1545
+
1546
+ return true;
1547
+
1548
+ } catch (error) {
1549
+ console.log(chalk.red(`āŒ Error installing skill: ${error.message}`));
1550
+ return false;
1551
+ }
1552
+ }
1553
+
1394
1554
  /**
1395
1555
  * Install multiple components with optional YAML workflow
1396
1556
  */
@@ -1403,7 +1563,8 @@ async function installMultipleComponents(options, targetDir) {
1403
1563
  commands: [],
1404
1564
  mcps: [],
1405
1565
  settings: [],
1406
- hooks: []
1566
+ hooks: [],
1567
+ skills: []
1407
1568
  };
1408
1569
 
1409
1570
  // Parse comma-separated values for each component type
@@ -1431,8 +1592,13 @@ async function installMultipleComponents(options, targetDir) {
1431
1592
  const hooksInput = Array.isArray(options.hook) ? options.hook.join(',') : options.hook;
1432
1593
  components.hooks = hooksInput.split(',').map(h => h.trim()).filter(h => h);
1433
1594
  }
1434
-
1435
- const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length;
1595
+
1596
+ if (options.skill) {
1597
+ const skillsInput = Array.isArray(options.skill) ? options.skill.join(',') : options.skill;
1598
+ components.skills = skillsInput.split(',').map(s => s.trim()).filter(s => s);
1599
+ }
1600
+
1601
+ const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length + components.skills.length;
1436
1602
 
1437
1603
  if (totalComponents === 0) {
1438
1604
  console.log(chalk.yellow('āš ļø No components specified to install.'));
@@ -1445,6 +1611,7 @@ async function installMultipleComponents(options, targetDir) {
1445
1611
  console.log(chalk.gray(` MCPs: ${components.mcps.length}`));
1446
1612
  console.log(chalk.gray(` Settings: ${components.settings.length}`));
1447
1613
  console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
1614
+ console.log(chalk.gray(` Skills: ${components.skills.length}`));
1448
1615
 
1449
1616
  // Counter for successfully installed components
1450
1617
  let successfullyInstalled = 0;
@@ -1526,14 +1693,21 @@ async function installMultipleComponents(options, targetDir) {
1526
1693
  // Install hooks (using shared installation locations)
1527
1694
  for (const hook of components.hooks) {
1528
1695
  console.log(chalk.gray(` Installing hook: ${hook}`));
1529
- const hookSuccess = await installIndividualHook(hook, targetDir, {
1530
- ...options,
1531
- silent: true,
1532
- sharedInstallLocations: sharedInstallLocations
1696
+ const hookSuccess = await installIndividualHook(hook, targetDir, {
1697
+ ...options,
1698
+ silent: true,
1699
+ sharedInstallLocations: sharedInstallLocations
1533
1700
  });
1534
1701
  if (hookSuccess > 0) successfullyInstalled++;
1535
1702
  }
1536
-
1703
+
1704
+ // Install skills
1705
+ for (const skill of components.skills) {
1706
+ console.log(chalk.gray(` Installing skill: ${skill}`));
1707
+ const skillSuccess = await installIndividualSkill(skill, targetDir, { ...options, silent: true });
1708
+ if (skillSuccess) successfullyInstalled++;
1709
+ }
1710
+
1537
1711
  // Handle YAML workflow if provided
1538
1712
  if (options.yaml) {
1539
1713
  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
+ });