Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
449
admin-ui/js/services/tools-service.js
Normal file
449
admin-ui/js/services/tools-service.js
Normal file
@@ -0,0 +1,449 @@
|
||||
/**
|
||||
* Tools Service - Manages MCP tools catalog and execution
|
||||
*/
|
||||
|
||||
import api from '../core/api.js';
|
||||
import logger from '../core/logger.js';
|
||||
|
||||
class ToolsService {
|
||||
constructor() {
|
||||
this.tools = [];
|
||||
this.toolsMetadata = this._getToolsMetadata();
|
||||
this.executionHistory = [];
|
||||
this.toolStatus = {};
|
||||
this.teamToolMappings = this._getTeamToolMappings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which tools are relevant for each team
|
||||
*/
|
||||
_getTeamToolMappings() {
|
||||
return {
|
||||
'ui': [
|
||||
'extract_tokens', 'extract_components', 'generate_component_code',
|
||||
'sync_tokens_to_file', 'ingest_css_tokens', 'ingest_scss_tokens',
|
||||
'ingest_tailwind_tokens', 'merge_tokens', 'export_tokens',
|
||||
'scan_storybook', 'generate_story', 'generate_stories_batch',
|
||||
'generate_storybook_theme'
|
||||
],
|
||||
'ux': [
|
||||
'analyze_style_values', 'check_naming_consistency', 'find_style_patterns',
|
||||
'validate_tokens', 'extract_tokens', 'export_tokens',
|
||||
'discover_project', 'get_sync_history'
|
||||
],
|
||||
'qa': [
|
||||
'get_quick_wins', 'get_quick_wins_report', 'validate_tokens',
|
||||
'find_unused_styles', 'find_inline_styles', 'analyze_react_components',
|
||||
'build_source_graph', 'get_story_coverage'
|
||||
],
|
||||
'all': [] // Empty means all tools
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for all 32 MCP tools
|
||||
*/
|
||||
_getToolsMetadata() {
|
||||
return {
|
||||
// Projects
|
||||
list_projects: {
|
||||
name: 'list_projects',
|
||||
category: 'Projects',
|
||||
description: 'List all design system projects',
|
||||
parameters: [],
|
||||
icon: '📁'
|
||||
},
|
||||
create_project: {
|
||||
name: 'create_project',
|
||||
category: 'Projects',
|
||||
description: 'Create a new design system project',
|
||||
parameters: ['name', 'description', 'figma_file_key'],
|
||||
icon: '➕'
|
||||
},
|
||||
get_project: {
|
||||
name: 'get_project',
|
||||
category: 'Projects',
|
||||
description: 'Get details of a specific project',
|
||||
parameters: ['project_id'],
|
||||
icon: '🔍'
|
||||
},
|
||||
|
||||
// Figma
|
||||
extract_tokens: {
|
||||
name: 'extract_tokens',
|
||||
category: 'Figma',
|
||||
description: 'Extract design tokens from Figma file',
|
||||
parameters: ['file_key', 'format'],
|
||||
icon: '🎨',
|
||||
requiresConfig: ['figma_token']
|
||||
},
|
||||
extract_components: {
|
||||
name: 'extract_components',
|
||||
category: 'Figma',
|
||||
description: 'Extract components from Figma file',
|
||||
parameters: ['file_key'],
|
||||
icon: '🧩',
|
||||
requiresConfig: ['figma_token']
|
||||
},
|
||||
generate_component_code: {
|
||||
name: 'generate_component_code',
|
||||
category: 'Figma',
|
||||
description: 'Generate React code from Figma component',
|
||||
parameters: ['file_key', 'node_id', 'framework'],
|
||||
icon: '⚛️',
|
||||
requiresConfig: ['figma_token']
|
||||
},
|
||||
sync_tokens_to_file: {
|
||||
name: 'sync_tokens_to_file',
|
||||
category: 'Figma',
|
||||
description: 'Sync tokens to output file',
|
||||
parameters: ['file_key', 'output_path', 'format'],
|
||||
icon: '🔄',
|
||||
requiresConfig: ['figma_token']
|
||||
},
|
||||
|
||||
// Ingestion
|
||||
ingest_css_tokens: {
|
||||
name: 'ingest_css_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Ingest tokens from CSS variables',
|
||||
parameters: ['source'],
|
||||
icon: '📥'
|
||||
},
|
||||
ingest_scss_tokens: {
|
||||
name: 'ingest_scss_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Ingest tokens from SCSS variables',
|
||||
parameters: ['source'],
|
||||
icon: '📥'
|
||||
},
|
||||
ingest_tailwind_tokens: {
|
||||
name: 'ingest_tailwind_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Ingest tokens from Tailwind config',
|
||||
parameters: ['source'],
|
||||
icon: '📥'
|
||||
},
|
||||
ingest_json_tokens: {
|
||||
name: 'ingest_json_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Ingest tokens from JSON format',
|
||||
parameters: ['source'],
|
||||
icon: '📥'
|
||||
},
|
||||
merge_tokens: {
|
||||
name: 'merge_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Merge tokens from multiple sources',
|
||||
parameters: ['sources', 'strategy'],
|
||||
icon: '🔗'
|
||||
},
|
||||
export_tokens: {
|
||||
name: 'export_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Export tokens to specified format',
|
||||
parameters: ['format', 'output_path'],
|
||||
icon: '📤'
|
||||
},
|
||||
validate_tokens: {
|
||||
name: 'validate_tokens',
|
||||
category: 'Ingestion',
|
||||
description: 'Validate token structure and values',
|
||||
parameters: ['source'],
|
||||
icon: '✓'
|
||||
},
|
||||
|
||||
// Analysis
|
||||
discover_project: {
|
||||
name: 'discover_project',
|
||||
category: 'Analysis',
|
||||
description: 'Discover project structure and frameworks',
|
||||
parameters: ['path'],
|
||||
icon: '🔎',
|
||||
quickWin: true
|
||||
},
|
||||
analyze_react_components: {
|
||||
name: 'analyze_react_components',
|
||||
category: 'Analysis',
|
||||
description: 'Analyze React components in project',
|
||||
parameters: ['path'],
|
||||
icon: '⚛️',
|
||||
quickWin: true
|
||||
},
|
||||
find_inline_styles: {
|
||||
name: 'find_inline_styles',
|
||||
category: 'Analysis',
|
||||
description: 'Find components with inline styles',
|
||||
parameters: ['path'],
|
||||
icon: '🎯',
|
||||
quickWin: true
|
||||
},
|
||||
find_style_patterns: {
|
||||
name: 'find_style_patterns',
|
||||
category: 'Analysis',
|
||||
description: 'Identify common style patterns',
|
||||
parameters: ['path'],
|
||||
icon: '📊',
|
||||
quickWin: true
|
||||
},
|
||||
analyze_style_values: {
|
||||
name: 'analyze_style_values',
|
||||
category: 'Analysis',
|
||||
description: 'Analyze style property values',
|
||||
parameters: ['path', 'property'],
|
||||
icon: '📈',
|
||||
quickWin: true
|
||||
},
|
||||
find_unused_styles: {
|
||||
name: 'find_unused_styles',
|
||||
category: 'Analysis',
|
||||
description: 'Find unused style definitions',
|
||||
parameters: ['path'],
|
||||
icon: '🗑️',
|
||||
quickWin: true
|
||||
},
|
||||
build_source_graph: {
|
||||
name: 'build_source_graph',
|
||||
category: 'Analysis',
|
||||
description: 'Build component dependency graph',
|
||||
parameters: ['path', 'depth'],
|
||||
icon: '🕸️'
|
||||
},
|
||||
get_quick_wins: {
|
||||
name: 'get_quick_wins',
|
||||
category: 'Analysis',
|
||||
description: 'Get actionable improvement opportunities',
|
||||
parameters: ['path'],
|
||||
icon: '⚡',
|
||||
quickWin: true,
|
||||
featured: true
|
||||
},
|
||||
get_quick_wins_report: {
|
||||
name: 'get_quick_wins_report',
|
||||
category: 'Analysis',
|
||||
description: 'Generate detailed quick wins report',
|
||||
parameters: ['path'],
|
||||
icon: '📋',
|
||||
quickWin: true
|
||||
},
|
||||
check_naming_consistency: {
|
||||
name: 'check_naming_consistency',
|
||||
category: 'Analysis',
|
||||
description: 'Check component naming consistency',
|
||||
parameters: ['path'],
|
||||
icon: '🏷️',
|
||||
quickWin: true
|
||||
},
|
||||
|
||||
// Storybook
|
||||
scan_storybook: {
|
||||
name: 'scan_storybook',
|
||||
category: 'Storybook',
|
||||
description: 'Scan existing Storybook configuration',
|
||||
parameters: ['path'],
|
||||
icon: '📚'
|
||||
},
|
||||
generate_story: {
|
||||
name: 'generate_story',
|
||||
category: 'Storybook',
|
||||
description: 'Generate Storybook story for component',
|
||||
parameters: ['component_path', 'template'],
|
||||
icon: '📖'
|
||||
},
|
||||
generate_stories_batch: {
|
||||
name: 'generate_stories_batch',
|
||||
category: 'Storybook',
|
||||
description: 'Generate stories for multiple components',
|
||||
parameters: ['components', 'template'],
|
||||
icon: '📚'
|
||||
},
|
||||
generate_storybook_theme: {
|
||||
name: 'generate_storybook_theme',
|
||||
category: 'Storybook',
|
||||
description: 'Generate Storybook theme from tokens',
|
||||
parameters: ['tokens', 'output_path'],
|
||||
icon: '🎨'
|
||||
},
|
||||
get_story_coverage: {
|
||||
name: 'get_story_coverage',
|
||||
category: 'Storybook',
|
||||
description: 'Check which components have stories',
|
||||
parameters: ['path'],
|
||||
icon: '📊'
|
||||
},
|
||||
|
||||
// Activity
|
||||
get_status: {
|
||||
name: 'get_status',
|
||||
category: 'Activity',
|
||||
description: 'Get server status and statistics',
|
||||
parameters: [],
|
||||
icon: '💚'
|
||||
},
|
||||
get_sync_history: {
|
||||
name: 'get_sync_history',
|
||||
category: 'Activity',
|
||||
description: 'Get token sync history',
|
||||
parameters: ['limit'],
|
||||
icon: '📜'
|
||||
},
|
||||
get_activity: {
|
||||
name: 'get_activity',
|
||||
category: 'Activity',
|
||||
description: 'Get recent activity log',
|
||||
parameters: ['limit'],
|
||||
icon: '📝'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools with metadata
|
||||
*/
|
||||
getAllTools() {
|
||||
return Object.values(this.toolsMetadata).map(tool => ({
|
||||
...tool,
|
||||
status: this.toolStatus[tool.name] || 'available',
|
||||
lastUsed: this._getLastUsed(tool.name)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools by category
|
||||
*/
|
||||
getToolsByCategory(category) {
|
||||
return this.getAllTools().filter(tool => tool.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quick win tools
|
||||
*/
|
||||
getQuickWinTools() {
|
||||
return this.getAllTools().filter(tool => tool.quickWin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get featured tools
|
||||
*/
|
||||
getFeaturedTools() {
|
||||
return this.getAllTools().filter(tool => tool.featured);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools filtered by team context
|
||||
*/
|
||||
getToolsByTeam(teamContext = 'all') {
|
||||
if (teamContext === 'all' || !this.teamToolMappings[teamContext]) {
|
||||
return this.getAllTools();
|
||||
}
|
||||
|
||||
const teamTools = this.teamToolMappings[teamContext];
|
||||
return this.getAllTools().filter(tool => teamTools.includes(tool.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a tool
|
||||
*/
|
||||
async executeTool(toolName, params = {}) {
|
||||
logger.info('Tools', `Executing tool: ${toolName}`, params);
|
||||
|
||||
this.toolStatus[toolName] = 'executing';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Map tool name to API endpoint
|
||||
const result = await api.executeMCPTool(toolName, params);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
this.toolStatus[toolName] = 'success';
|
||||
|
||||
// Log execution
|
||||
this._logExecution(toolName, params, result, duration, 'success');
|
||||
|
||||
logger.info('Tools', `Tool ${toolName} succeeded in ${duration}ms`);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
this.toolStatus[toolName] = 'error';
|
||||
|
||||
// Log error
|
||||
this._logExecution(toolName, params, null, duration, 'error', error.message);
|
||||
|
||||
logger.error('Tools', `Tool ${toolName} failed: ${error.message}`, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log tool execution
|
||||
*/
|
||||
_logExecution(toolName, params, result, duration, status, error = null) {
|
||||
const execution = {
|
||||
toolName,
|
||||
params,
|
||||
result,
|
||||
duration,
|
||||
status,
|
||||
error,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.executionHistory.push(execution);
|
||||
|
||||
// Keep only last 100 executions
|
||||
if (this.executionHistory.length > 100) {
|
||||
this.executionHistory.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get execution history for a tool
|
||||
*/
|
||||
getToolHistory(toolName) {
|
||||
return this.executionHistory
|
||||
.filter(exec => exec.toolName === toolName)
|
||||
.slice(-10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last used timestamp
|
||||
*/
|
||||
_getLastUsed(toolName) {
|
||||
const executions = this.executionHistory.filter(exec => exec.toolName === toolName);
|
||||
if (executions.length === 0) return null;
|
||||
return executions[executions.length - 1].timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool success rate
|
||||
*/
|
||||
getSuccessRate(toolName) {
|
||||
const executions = this.executionHistory.filter(exec => exec.toolName === toolName);
|
||||
if (executions.length === 0) return null;
|
||||
|
||||
const successful = executions.filter(exec => exec.status === 'success').length;
|
||||
return successful / executions.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all categories
|
||||
*/
|
||||
getCategories() {
|
||||
const categories = new Set();
|
||||
Object.values(this.toolsMetadata).forEach(tool => {
|
||||
categories.add(tool.category);
|
||||
});
|
||||
return Array.from(categories);
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
const toolsService = new ToolsService();
|
||||
|
||||
export default toolsService;
|
||||
export { ToolsService };
|
||||
Reference in New Issue
Block a user