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 +56 -0
- package/bin/create-claude-config.js +1 -0
- package/package.json +7 -2
- package/src/analytics-web/chats_mobile.html +17 -16
- package/src/console-bridge.js +3 -3
- package/src/index.js +183 -9
- package/src/security-audit.js +164 -0
- package/src/validation/ARCHITECTURE.md +309 -0
- package/src/validation/BaseValidator.js +152 -0
- package/src/validation/README.md +543 -0
- package/src/validation/ValidationOrchestrator.js +305 -0
- package/src/validation/validators/IntegrityValidator.js +338 -0
- package/src/validation/validators/ProvenanceValidator.js +399 -0
- package/src/validation/validators/ReferenceValidator.js +373 -0
- package/src/validation/validators/SemanticValidator.js +449 -0
- package/src/validation/validators/StructuralValidator.js +376 -0
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.
|
|
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">
|
package/src/console-bridge.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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
|
+
});
|