UNPKG

powr-sdk-api

Version:

Shared API core library for PowrStack projects. Zero dependencies - works with Express, Next.js API routes, and other frameworks. All features are optional and install only what you need.

503 lines (477 loc) 14.4 kB
"use strict"; const { getDb } = require("../services/mongo"); const crypto = require('crypto'); const { config } = require('../config'); class ToolsManager { constructor() { this.registeredTools = new Map(); this.isInitialized = false; this.isCentralService = false; this.isEnabled = false; // Default disabled } async initialize(options = {}) { this.isCentralService = options.isCentralService || false; this.isEnabled = options.enableTools === true; // Default false unless explicitly enabled if (!this.isEnabled) { console.log("🚫 Tools is disabled"); this.isInitialized = true; return; } if (this.isCentralService) { // Central service: Don't pre-load, load dynamically console.log("🔄 Central service mode - loading tools dynamically"); this.isInitialized = true; } else { // Individual API: Load all available tools console.log("📦 Loading tools registry..."); try { const db = await getDb(); const tools = await db.collection("tools").find({}).toArray(); tools.forEach(tool => { this.registerTool(tool); }); this.isInitialized = true; console.log(`✅ Loaded ${tools.length} tools from registry`); } catch (error) { console.error("❌ Failed to initialize tools:", error); this.isInitialized = true; // Still mark as initialized to prevent blocking } } } // Register a new tool registerTool(tool) { var _tool$_id; const toolId = tool.id || ((_tool$_id = tool._id) === null || _tool$_id === void 0 ? void 0 : _tool$_id.toString()); if (!toolId) { console.error("❌ Tool must have an ID"); return; } this.registeredTools.set(toolId, { ...tool, registeredAt: new Date(), isActive: true }); console.log(`✅ Registered tool: ${tool.name} (${toolId})`); } // Get all available tools getAllTools() { return Array.from(this.registeredTools.values()).filter(tool => tool.isActive); } // Get tool by ID getTool(toolId) { return this.registeredTools.get(toolId); } // Get tools by category getToolsByCategory(category) { return this.getAllTools().filter(tool => tool.category === category); } // Get user's configured tools async getUserTools(userId) { if (!this.isEnabled) { return []; } try { const db = await getDb(); const userTools = await db.collection("user_tools").find({ userId }).toArray(); return userTools; } catch (error) { console.error("❌ Failed to get user tools:", error); return []; } } // Configure tool for user async configureTool(userId, toolId, config) { if (!this.isEnabled) { return { success: false, message: "Tools is disabled" }; } try { const db = await getDb(); const tool = this.getTool(toolId); if (!tool) { return { success: false, message: "Tool not found" }; } // Validate configuration against tool schema if (!this.validateToolConfig(tool, config)) { return { success: false, message: "Invalid configuration" }; } // Encrypt sensitive data const encryptedConfig = this.encryptConfig(config); const userTool = { userId, toolId, config: encryptedConfig, enabled: true, createdAt: new Date(), updatedAt: new Date() }; await db.collection("user_tools").updateOne({ userId, toolId }, { $set: userTool }, { upsert: true }); console.log(`✅ Configured tool ${toolId} for user ${userId}`); return { success: true, message: "Tool configured successfully" }; } catch (error) { console.error("❌ Failed to configure tool:", error); return { success: false, message: "Failed to configure tool" }; } } // Execute tool action async executeToolAction(options = {}) { const { userId, toolId, actionId, params } = options; if (!this.isEnabled) { return { success: false, message: "Tools is disabled" }; } try { const tool = this.getTool(toolId); if (!tool) { return { success: false, message: "Tool not found" }; } // Get user's tool configuration const db = await getDb(); const userTool = await db.collection("user_tools").findOne({ userId, toolId }); if (!userTool || !userTool.enabled) { return { success: false, message: "Tool not configured or disabled" }; } // Decrypt configuration const config = this.decryptConfig(userTool.config); // Execute the action const result = await this.executeAction(tool, actionId, params, config); // Log execution await this.logToolExecution(userId, toolId, actionId, params, result); return result; } catch (error) { console.error("❌ Failed to execute tool action:", error); return { success: false, message: "Failed to execute tool action" }; } } // Test tool connection async testToolConnection(userId, toolId) { if (!this.isEnabled) { return { success: false, message: "Tools is disabled" }; } try { const tool = this.getTool(toolId); if (!tool) { return { success: false, message: "Tool not found" }; } // Get user's tool configuration const db = await getDb(); const userTool = await db.collection("user_tools").findOne({ userId, toolId }); if (!userTool) { return { success: false, message: "Tool not configured" }; } // Decrypt configuration const config = this.decryptConfig(userTool.config); // Test connection const result = await this.testConnection(tool, config); return result; } catch (error) { console.error("❌ Failed to test tool connection:", error); return { success: false, message: "Failed to test tool connection" }; } } // Toggle tool for user async toggleTool(userId, toolId, enabled) { if (!this.isEnabled) { return { success: false, message: "Tools is disabled" }; } try { const db = await getDb(); await db.collection("user_tools").updateOne({ userId, toolId }, { $set: { enabled, updatedAt: new Date() } }); console.log(`${enabled ? '✅' : '❌'} Tool ${toolId} ${enabled ? 'enabled' : 'disabled'} for user ${userId}`); return { success: true, message: `Tool ${enabled ? 'enabled' : 'disabled'} successfully` }; } catch (error) { console.error("❌ Failed to toggle tool:", error); return { success: false, message: "Failed to toggle tool" }; } } // Helper methods validateToolConfig(tool, config) { // Basic validation - can be extended based on tool schema if (!tool.configSchema) return true; // Validate required fields for (const [field, schema] of Object.entries(tool.configSchema)) { if (schema.required && !config[field]) { return false; } } return true; } encryptConfig(config) { // Simple encryption - in production, use proper encryption return Buffer.from(JSON.stringify(config)).toString('base64'); } decryptConfig(encryptedConfig) { // Simple decryption - in production, use proper decryption return JSON.parse(Buffer.from(encryptedConfig, 'base64').toString()); } async executeAction(tool, actionId, params, config) { try { switch (tool.name.toLowerCase()) { case 'gmail': return await this.executeGmailAction(actionId, params, config); case 'weather': case 'openweather': return await this.executeWeatherAction(actionId, params, config, tool); case 'slack': return await this.executeSlackAction(actionId, params, config); case 'github': return await this.executeGitHubAction(actionId, params, config); default: return { success: false, message: `Tool ${tool.name} not implemented yet` }; } } catch (error) { console.error(`❌ Error executing ${tool.name} action:`, error); return { success: false, message: `Failed to execute ${actionId} on ${tool.name}: ${error.message}` }; } } async executeGmailAction(actionId, params, config) { const nodemailer = require('nodemailer'); try { // Create transporter using the exact config from ai-pitcher const transporter = nodemailer.createTransport({ host: 'smtp.gmail.com', port: 465, secure: true, auth: { user: config.email, pass: config.appPassword } }); switch (actionId) { case 'send-email': const mailOptions = { from: config.email, to: params.to, subject: params.subject, text: params.body }; const result = await transporter.sendMail(mailOptions); return { success: true, message: `Email sent successfully to ${params.to}`, data: { messageId: result.messageId, to: params.to, subject: params.subject } }; default: return { success: false, message: `Unknown Gmail action: ${actionId}` }; } } catch (error) { console.error('❌ Gmail execution error:', error); return { success: false, message: `Failed to send email: ${error.message}` }; } } async executeWeatherAction(actionId, params, config, tool) { const axios = require('axios'); try { var _tool$systemConfig; // Get weather data from system config (database) or fallback to env var const weatherApiKey = (tool === null || tool === void 0 || (_tool$systemConfig = tool.systemConfig) === null || _tool$systemConfig === void 0 ? void 0 : _tool$systemConfig.apiKey) || process.env.WEATHER_API_KEY || 'demo'; const location = config.location; let weatherData; if (weatherApiKey === 'demo') { // Demo weather data for testing weatherData = { weather: [{ main: params.condition || 'Rain', description: 'light rain' }] }; console.log('🌤️ Using demo weather data'); } else { // Real weather API call const weatherResponse = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(location)}&appid=${weatherApiKey}&units=metric`); weatherData = weatherResponse.data; } // Check if condition matches const currentWeather = weatherData.weather[0].main.toLowerCase(); const targetCondition = ((params === null || params === void 0 ? void 0 : params.condition) || 'rain').toLowerCase(); const operator = (params === null || params === void 0 ? void 0 : params.operator) || 'is'; let conditionMet = false; switch (operator) { case 'is': conditionMet = currentWeather === targetCondition; break; case 'is_not': conditionMet = currentWeather !== targetCondition; break; case 'contains': conditionMet = currentWeather.includes(targetCondition) || weatherData.weather[0].description.toLowerCase().includes(targetCondition); break; default: conditionMet = currentWeather === targetCondition; } return { success: true, message: `Weather check completed. Current: ${currentWeather}, Required: ${targetCondition}`, data: { currentWeather: currentWeather, requiredCondition: targetCondition, conditionMet: conditionMet, location: location } }; } catch (error) { console.error('❌ Weather check error:', error); return { success: false, message: `Failed to check weather: ${error.message}` }; } } async executeSlackAction(actionId, params, config) { // TODO: Implement Slack actions return { success: false, message: "Slack integration not implemented yet" }; } async executeGitHubAction(actionId, params, config) { // TODO: Implement GitHub actions return { success: false, message: "GitHub integration not implemented yet" }; } async testConnection(tool, config) { // This would test the actual connection based on tool type // For now, return a mock response return { success: true, message: `Connection to ${tool.name} successful` }; } async logToolExecution(userId, toolId, actionId, params, result) { try { const db = await getDb(); await db.collection("tool_executions").insertOne({ userId, toolId, actionId, params, result, timestamp: new Date() }); } catch (error) { console.error("❌ Failed to log tool execution:", error); } } // Get statistics getStats() { return { isEnabled: this.isEnabled, isInitialized: this.isInitialized, totalTools: this.registeredTools.size, activeTools: this.getAllTools().length }; } // Enable/disable tools enable() { this.isEnabled = true; console.log("✅ Tools enabled"); } disable() { this.isEnabled = false; console.log("❌ Tools disabled"); } toggle() { this.isEnabled = !this.isEnabled; console.log(`${this.isEnabled ? '✅' : '❌'} Tools ${this.isEnabled ? 'enabled' : 'disabled'}`); return this.isEnabled; } } // Create and export singleton instance const toolsManager = new ToolsManager(); module.exports = toolsManager;