chore: import upstream snapshot with attribution
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:58 +08:00
commit bb5c75ce05
8824 changed files with 1946442 additions and 0 deletions
+620
View File
@@ -0,0 +1,620 @@
// Workflow Builder JavaScript
// Global state
let workflowState = {
steps: [],
properties: {
name: '',
description: '',
tags: []
},
components: {
agents: [],
commands: [],
mcps: []
}
};
// GitHub API configuration
const GITHUB_CONFIG = {
owner: 'davila7',
repo: 'claude-code-templates',
branch: 'main'
};
// Initialize the workflow builder
document.addEventListener('DOMContentLoaded', async function() {
showLoadingSpinner();
try {
await loadAllComponents();
initializeDragAndDrop();
initializeEventListeners();
initializeModalEventListeners();
initializeSortableSteps();
} catch (error) {
console.error('Error initializing Workflow Builder:', error);
showError('Failed to load components. Please refresh the page.');
} finally {
hideLoadingSpinner();
}
});
// Load all components from GitHub using the Git Trees API
async function loadAllComponents() {
try {
const response = await fetch('components.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const allComponents = await response.json();
workflowState.components = {
agents: allComponents.agents.sort((a, b) => a.path.localeCompare(b.path)),
commands: allComponents.commands.sort((a, b) => a.path.localeCompare(b.path)),
mcps: allComponents.mcps.sort((a, b) => a.path.localeCompare(b.path))
};
renderComponentsList('agents', workflowState.components.agents);
renderComponentsList('commands', workflowState.components.commands);
renderComponentsList('mcps', workflowState.components.mcps);
} catch (error) {
console.error('Error loading components:', error);
loadComponentsWithFallback();
}
}
async function loadComponentsWithFallback() {
const [agents, commands, mcps] = [
getFallbackComponentData('agents'),
getFallbackComponentData('commands'),
getFallbackComponentData('mcps')
];
workflowState.components = { agents, commands, mcps };
renderComponentsList('agents', agents);
renderComponentsList('commands', commands);
renderComponentsList('mcps', mcps);
}
// Get fallback component data
function getFallbackComponentData(type) {
const fallbackData = {
agents: [
{ name: 'code-reviewer', path: 'development/code-reviewer', category: 'development', type: 'agent', icon: '🤖' },
{ name: 'documentation-writer', path: 'development/documentation-writer', category: 'development', type: 'agent', icon: '🤖' },
{ name: 'bug-hunter', path: 'development/bug-hunter', category: 'development', type: 'agent', icon: '🤖' },
{ name: 'security-auditor', path: 'security/security-auditor', category: 'security', type: 'agent', icon: '🤖' },
{ name: 'performance-optimizer', path: 'optimization/performance-optimizer', category: 'optimization', type: 'agent', icon: '🤖' }
],
commands: [
{ name: 'git-setup', path: 'development/git-setup', category: 'development', type: 'command', icon: '⚡' },
{ name: 'project-init', path: 'development/project-init', category: 'development', type: 'command', icon: '⚡' },
{ name: 'docker-setup', path: 'devops/docker-setup', category: 'devops', type: 'command', icon: '⚡' },
{ name: 'test-runner', path: 'testing/test-runner', category: 'testing', type: 'command', icon: '⚡' },
{ name: 'build-pipeline', path: 'devops/build-pipeline', category: 'devops', type: 'command', icon: '⚡' }
],
mcps: [
{ name: 'database-connector', path: 'database/database-connector', category: 'database', type: 'mcp', icon: '🔌' },
{ name: 'api-client', path: 'api/api-client', category: 'api', type: 'mcp', icon: '🔌' },
{ name: 'file-manager', path: 'system/file-manager', category: 'system', type: 'mcp', icon: '🔌' },
{ name: 'redis-cache', path: 'database/redis-cache', category: 'database', type: 'mcp', icon: '🔌' },
{ name: 'email-service', path: 'communication/email-service', category: 'communication', type: 'mcp', icon: '🔌' }
]
};
const typeKey = type;
const data = fallbackData[typeKey] || [];
return data;
}
// Get icon for component type
function getComponentIcon(type) {
const icons = { 'agent': '🤖', 'command': '⚡', 'mcp': '🔌' };
return icons[type] || '📦';
}
// Render components list in the UI
function renderComponentsList(type, components) {
const container = document.getElementById(`${type}-tree`);
const countElement = document.getElementById(`${type}-count`);
if (!container || !countElement) return;
countElement.textContent = components.length;
container.innerHTML = '';
const groupedComponents = components.reduce((acc, component) => {
const category = component.category === 'root' ? 'general' : component.category;
if (!acc[category]) acc[category] = [];
acc[category].push(component);
return acc;
}, {});
Object.entries(groupedComponents).forEach(([category, categoryComponents]) => {
const folderElement = createTreeFolder(category, categoryComponents, type);
container.appendChild(folderElement);
});
}
// Create tree folder element
function createTreeFolder(category, components, type) {
const folderElement = document.createElement('div');
folderElement.className = 'tree-node';
const folderId = `${type}-${category.replace(/[^a-z0-9]/gi, '_').toLowerCase()}`;
folderElement.innerHTML = `
<div class="tree-node-header" data-folder="${folderId}">
<span class="tree-icon folder-icon">📁</span>
<span class="tree-label">${category}</span>
<span class="tree-count">${components.length}</span>
<span class="tree-arrow">▶</span>
</div>
<div class="tree-children" id="${folderId}"></div>
`;
const childrenContainer = folderElement.querySelector('.tree-children');
components.forEach(component => {
const fileElement = createTreeFile(component);
childrenContainer.appendChild(fileElement);
});
return folderElement;
}
// Create tree file element
function createTreeFile(component) {
const element = document.createElement('div');
element.className = 'tree-file';
element.draggable = true;
element.dataset.componentType = component.type;
element.dataset.componentName = component.name;
element.dataset.componentPath = component.path;
element.dataset.componentCategory = component.category;
element.innerHTML = `
<div class="tree-file-header">
<span class="tree-icon file-icon">${getFileIcon(component.type)}</span>
<span class="tree-file-name">${component.name}</span>
<div class="tree-file-actions">
<button class="tree-action-btn" title="View Details" onclick="showComponentDetails('${component.type}', '${component.name}', '${component.path}', '${component.category}')">️</button>
<button class="tree-action-btn" title="Add to Workflow" onclick="addComponentFromButton(event)"></button>
</div>
</div>
`;
return element;
}
// Get file icon based on type
function getFileIcon(type) {
const icons = { 'agent': '📄', 'command': '⚡', 'mcp': '⚙️' };
return icons[type] || '📄';
}
// Initialize drag and drop
function initializeDragAndDrop() {
const workflowSteps = document.getElementById('workflowSteps');
document.addEventListener('dragstart', (event) => {
if (event.target.closest('.tree-file')) {
handleDragStart(event);
}
});
workflowSteps.addEventListener('dragover', handleDragOver);
workflowSteps.addEventListener('drop', handleDrop);
workflowSteps.addEventListener('dragenter', (e) => e.target.closest('.drop-zone')?.classList.add('drag-over'));
workflowSteps.addEventListener('dragleave', (e) => e.target.closest('.drop-zone')?.classList.remove('drag-over'));
}
function handleDragStart(event) {
const treeFile = event.target.closest('.tree-file');
const componentData = { ...treeFile.dataset };
event.dataTransfer.setData('application/json', JSON.stringify(componentData));
event.dataTransfer.effectAllowed = 'copy';
}
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
}
function handleDrop(event) {
event.preventDefault();
event.target.closest('.drop-zone')?.classList.remove('drag-over');
try {
const componentData = JSON.parse(event.dataTransfer.getData('application/json'));
addWorkflowStep(componentData);
} catch (error) {
console.error('Error handling drop:', error);
}
}
function addComponentFromButton(event) {
const treeFile = event.target.closest('.tree-file');
const componentData = { ...treeFile.dataset };
addWorkflowStep(componentData);
}
function addWorkflowStep(componentData) {
const step = {
id: `step_${Date.now()}`,
type: componentData.componentType,
name: componentData.componentName,
path: componentData.componentPath,
category: componentData.componentCategory,
description: `Execute ${componentData.componentName}`,
icon: getComponentIcon(componentData.componentType)
};
workflowState.steps.push(step);
renderWorkflowSteps();
updateWorkflowStats();
}
function renderWorkflowSteps() {
const container = document.getElementById('workflowSteps');
const dropZone = container.querySelector('.drop-zone');
const existingSteps = container.querySelectorAll('.workflow-step');
existingSteps.forEach(step => step.remove());
if (workflowState.steps.length > 0) {
dropZone.style.display = 'none';
workflowState.steps.forEach((step, index) => {
const stepElement = document.createElement('div');
stepElement.className = 'workflow-step';
stepElement.dataset.stepId = step.id;
stepElement.innerHTML = `
<div class="step-number">${index + 1}</div>
<div class="step-icon">${step.icon}</div>
<div class="step-content">
<div class="step-name">${step.name}</div>
<div class="step-type">${step.type}</div>
</div>
<div class="step-actions">
<button class="step-action details" onclick="showComponentDetails('${step.type}', '${step.name}', '${step.path}', '${step.category}')">️</button>
<button class="step-action remove" onclick="removeStep('${step.id}')">🗑️</button>
</div>
`;
container.appendChild(stepElement);
});
} else {
dropZone.style.display = 'flex';
}
initializeSortableSteps();
}
function initializeSortableSteps() {
const workflowSteps = document.getElementById('workflowSteps');
if (workflowState.steps.length > 0) {
new Sortable(workflowSteps, {
animation: 150,
onEnd: (evt) => {
const movedStep = workflowState.steps.splice(evt.oldIndex, 1)[0];
workflowState.steps.splice(evt.newIndex, 0, movedStep);
renderWorkflowSteps();
}
});
}
}
function removeStep(stepId) {
workflowState.steps = workflowState.steps.filter(step => step.id !== stepId);
renderWorkflowSteps();
updateWorkflowStats();
}
function updateWorkflowStats() {
const stats = { agents: 0, commands: 0, mcps: 0 };
workflowState.steps.forEach(step => stats[step.type + 's']++);
document.getElementById('agentCount').textContent = stats.agents;
document.getElementById('commandCount').textContent = stats.commands;
document.getElementById('mcpCount').textContent = stats.mcps;
document.getElementById('totalSteps').textContent = workflowState.steps.length;
}
function initializeEventListeners() {
document.getElementById('componentSearch').addEventListener('input', handleComponentSearch);
document.getElementById('clearCanvas').addEventListener('click', clearWorkflow);
document.getElementById('generateWorkflow').addEventListener('click', openPropertiesModal);
document.getElementById('closePropertiesModal').addEventListener('click', closePropertiesModal);
document.getElementById('saveWorkflowProperties').addEventListener('click', saveAndGenerateWorkflow);
// Category accordion toggle
document.querySelectorAll('.category-card-header').forEach(header => {
header.addEventListener('click', () => {
header.parentElement.classList.toggle('expanded');
});
});
// Add event listener for sub-folders
document.addEventListener('click', function(event) {
const header = event.target.closest('.tree-node-header');
if (header) {
const children = header.nextElementSibling;
if (children && children.classList.contains('tree-children')) {
header.classList.toggle('expanded');
children.classList.toggle('expanded');
}
}
});
}
function handleComponentSearch(event) {
const searchTerm = event.target.value.toLowerCase();
document.querySelectorAll('.tree-file').forEach(file => {
const match = file.dataset.componentName.toLowerCase().includes(searchTerm);
file.style.display = match ? '' : 'none';
});
}
function clearWorkflow() {
if (confirm('Are you sure you want to clear the workflow?')) {
workflowState.steps = [];
renderWorkflowSteps();
updateWorkflowStats();
}
}
function openPropertiesModal() {
if (workflowState.steps.length === 0) {
showError('Add at least one component to the workflow.');
return;
}
document.getElementById('propertiesModal').style.display = 'block';
}
function closePropertiesModal() {
document.getElementById('propertiesModal').style.display = 'none';
}
function saveAndGenerateWorkflow() {
workflowState.properties.name = document.getElementById('workflowName').value;
workflowState.properties.description = document.getElementById('workflowDescription').value;
workflowState.properties.tags = document.getElementById('workflowTags').value.split(',').map(t => t.trim());
if (!workflowState.properties.name) {
showError('Workflow name is required.');
return;
}
closePropertiesModal();
generateWorkflow();
}
async function generateWorkflow() {
showLoadingSpinner();
try {
const workflowHash = await generateWorkflowHash();
const yamlContent = generateWorkflowYAML();
showGenerateModal(workflowHash, yamlContent);
} catch (error) {
console.error('Error generating workflow:', error);
} finally {
hideLoadingSpinner();
}
}
async function generateWorkflowHash() {
const dataString = JSON.stringify(workflowState);
const hash = CryptoJS.SHA256(dataString).toString(CryptoJS.enc.Hex).substring(0, 12);
localStorage.setItem(`workflow_${hash}`, dataString);
return hash;
}
function generateWorkflowYAML() {
return `# Workflow: ${workflowState.properties.name}\n` +
`steps:\n` +
workflowState.steps.map((step, i) => ` - step: ${i+1}\n type: ${step.type}\n name: "${step.name}"`).join('\n');
}
function showGenerateModal(hash, yaml) {
document.getElementById('workflowCommand').textContent = `npx claude-code-templates@latest --workflow:#${hash}`;
document.getElementById('yamlContent').textContent = yaml;
document.getElementById('generateModal').style.display = 'block';
}
function showLoadingSpinner() { document.getElementById('loadingSpinner').style.display = 'flex'; }
function hideLoadingSpinner() { document.getElementById('loadingSpinner').style.display = 'none'; }
function showError(msg) { alert(msg); }
async function showComponentDetails(type, name, path, category) {
const component = workflowState.components[type + 's'].find(c => c.name === name);
if (!component) {
console.warn('Component not found:', type, name);
return;
}
showComponentModal(component);
}
// Show detailed component modal (recreated from original version)
function showComponentModal(component) {
const typeConfig = {
agent: { icon: '🤖', color: '#ff6b6b', label: 'AGENT' },
command: { icon: '⚡', color: '#4ecdc4', label: 'COMMAND' },
mcp: { icon: '🔌', color: '#45b7d1', label: 'MCP' }
};
const config = typeConfig[component.type];
const installCommand = generateInstallCommand(component);
const modalHTML = `
<div class="modal-overlay" onclick="closeComponentModal()">
<div class="modal-content component-modal" onclick="event.stopPropagation()">
<div class="modal-header">
<div class="component-modal-title">
<span class="component-icon" style="color: ${config.color}">${config.icon}</span>
<h3>${formatComponentName(component.name)}</h3>
<span class="component-type-badge" style="background: ${config.color}">${config.label}</span>
</div>
<button class="modal-close" onclick="closeComponentModal()">×</button>
</div>
<div class="modal-body">
<div class="component-details">
<div class="component-description">
${getComponentDescription(component)}
</div>
<div class="installation-section">
<h4>📦 Installation</h4>
<div class="command-line">
<code>${installCommand}</code>
<button class="copy-btn" onclick="copyToClipboard('${installCommand}')">Copy</button>
</div>
</div>
<div class="component-content">
<h4>📋 Component Details</h4>
<div class="component-preview">
<pre><code>${component.content ? component.content.substring(0, 500) + (component.content.length > 500 ? '...' : '') : 'No content available.'}</code></pre>
</div>
</div>
<div class="modal-actions">
<button class="github-folder-link" onclick="viewOnGitHub('${component.path}')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.30.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
View on GitHub
</button>
</div>
</div>
</div>
</div>
</div>
`;
// Remove existing modal if present
const existingModal = document.querySelector('.modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Add modal to body
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
// Helper functions for the modal
function formatComponentName(name) {
return name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
function generateInstallCommand(component) {
return `npx claude-code-templates@latest --${component.type} "${component.path}"`;
}
function getComponentDescription(component) {
if (!component.content) {
return 'No description available.';
}
// Try to extract description from frontmatter
const descMatch = component.content.match(/description:\s*(.+?)(?:\n|$)/);
if (descMatch) {
return descMatch[1].trim().replace(/^["']|["']$/g, '');
}
// Use first paragraph if no frontmatter description
const lines = component.content.split('\n');
const firstParagraph = lines.find(line => line.trim() && !line.startsWith('---') && !line.startsWith('#'));
if (firstParagraph) {
return firstParagraph.trim();
}
return 'No description available.';
}
function viewOnGitHub(path) {
const url = `https://github.com/davila7/claude-code-templates/tree/main/${path}`;
window.open(url, '_blank');
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showSuccess('Command copied to clipboard!');
}).catch(err => {
console.error('Failed to copy: ', err);
});
}
function closeComponentModal() {
const modalOverlay = document.querySelector('.modal-overlay');
if (modalOverlay) {
modalOverlay.remove();
}
}
function initializeModalEventListeners() {
// Component modal event listeners
document.getElementById('closeComponentModal').addEventListener('click', closeComponentModal);
document.getElementById('closeComponentModalBtn').addEventListener('click', closeComponentModal);
document.getElementById('addComponentToWorkflow').addEventListener('click', () => {
const componentData = JSON.parse(document.getElementById('componentModal').dataset.currentComponent);
addWorkflowStep({
componentType: componentData.type,
componentName: componentData.name,
componentPath: componentData.path,
componentCategory: componentData.category
});
closeComponentModal();
});
document.getElementById('copyUsageCommand').addEventListener('click', () => {
const command = document.getElementById('componentModalUsage').textContent;
navigator.clipboard.writeText(command).then(() => showSuccess('Command copied!'));
});
// Generate modal event listeners
const closeModal = document.getElementById('closeModal');
if (closeModal) {
closeModal.addEventListener('click', () => {
document.getElementById('generateModal').style.display = 'none';
});
}
const copyCommand = document.getElementById('copyCommand');
if (copyCommand) {
copyCommand.addEventListener('click', () => {
const command = document.getElementById('workflowCommand').textContent;
navigator.clipboard.writeText(command).then(() => showSuccess('Command copied!'));
});
}
const copyYaml = document.getElementById('copyYaml');
if (copyYaml) {
copyYaml.addEventListener('click', () => {
const yaml = document.getElementById('yamlContent').textContent;
navigator.clipboard.writeText(yaml).then(() => showSuccess('YAML copied!'));
});
}
// Close modals when clicking outside
window.addEventListener('click', (event) => {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
});
}
function showSuccess(message) {
// Simple success notification
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #10b981;
color: white;
padding: 12px 24px;
border-radius: 6px;
z-index: 10000;
font-family: system-ui, -apple-system, sans-serif;
`;
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
}, 2000);
}
// Add functions to window object for inline event handlers
window.removeStep = removeStep;
window.showComponentDetails = showComponentDetails;
window.addComponentFromButton = addComponentFromButton;