echo-ai-agent 1.0.0
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/.env.example +1 -0
- package/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/cli.js +170 -0
- package/main/main.js +206 -0
- package/main/preload.js +10 -0
- package/package.json +57 -0
- package/plugins/example-plugin.js +100 -0
- package/scripts/config-manager.js +86 -0
- package/scripts/plugin-manager.js +160 -0
- package/scripts/postinstall.js +30 -0
- package/scripts/prepublish.js +63 -0
- package/scripts/setup-wizard.js +97 -0
- package/scripts/uninstall.js +36 -0
- package/services/gemini.js +79 -0
- package/services/system.js +181 -0
- package/services/whisper.js +24 -0
- package/ui/index.html +40 -0
- package/ui/renderer.js +151 -0
- package/ui/style.css +117 -0
package/main/main.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const { app, BrowserWindow, ipcMain, screen, globalShortcut, Menu } = require('electron');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const GeminiBrain = require('../services/gemini');
|
|
4
|
+
const SystemActions = require('../services/system');
|
|
5
|
+
const ConfigManager = require('../scripts/config-manager');
|
|
6
|
+
const PluginManager = require('../scripts/plugin-manager');
|
|
7
|
+
require('dotenv').config();
|
|
8
|
+
|
|
9
|
+
let mainWindow;
|
|
10
|
+
let brain;
|
|
11
|
+
const config = new ConfigManager();
|
|
12
|
+
const pluginManager = new PluginManager();
|
|
13
|
+
|
|
14
|
+
async function initializeBrain() {
|
|
15
|
+
// ... existing initialization code
|
|
16
|
+
const apiKey = config.get('apiKey') || process.env.GOOGLE_AI_API_KEY;
|
|
17
|
+
|
|
18
|
+
if (apiKey) {
|
|
19
|
+
// Load plugins first
|
|
20
|
+
await pluginManager.loadPlugins();
|
|
21
|
+
|
|
22
|
+
// Convert plugin commands to Gemini tool format
|
|
23
|
+
const pluginTools = pluginManager.listPlugins().filter(p => p.enabled).flatMap(p => {
|
|
24
|
+
const plugin = pluginManager.plugins.get(p.name);
|
|
25
|
+
return Object.keys(plugin.commands).map(cmdName => ({
|
|
26
|
+
name: cmdName, // Command name matches function name
|
|
27
|
+
description: plugin.commandDescriptions?.[cmdName] || `Execute ${cmdName} command`,
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "OBJECT",
|
|
30
|
+
properties: {
|
|
31
|
+
args: { type: "STRING", description: "Arguments for the command" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
brain = new GeminiBrain(apiKey, pluginTools);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createWindow() {
|
|
42
|
+
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
|
43
|
+
|
|
44
|
+
// Get user preferences
|
|
45
|
+
const windowSize = config.getWindowSize(config.get('size') || 'medium');
|
|
46
|
+
const position = config.getWindowPosition(
|
|
47
|
+
config.get('position') || 'bottom-right',
|
|
48
|
+
{ width, height },
|
|
49
|
+
windowSize
|
|
50
|
+
);
|
|
51
|
+
const alwaysOnTop = config.get('alwaysOnTop') !== false;
|
|
52
|
+
const startOnBoot = config.get('startOnBoot') === true;
|
|
53
|
+
|
|
54
|
+
// Apply start on boot setting
|
|
55
|
+
app.setLoginItemSettings({
|
|
56
|
+
openAtLogin: startOnBoot,
|
|
57
|
+
path: app.getPath('exe') // Correct specialized path for packaged app
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
mainWindow = new BrowserWindow({
|
|
61
|
+
width: windowSize.width,
|
|
62
|
+
height: windowSize.height,
|
|
63
|
+
x: position.x,
|
|
64
|
+
y: position.y,
|
|
65
|
+
frame: false,
|
|
66
|
+
transparent: true,
|
|
67
|
+
alwaysOnTop: alwaysOnTop,
|
|
68
|
+
webPreferences: {
|
|
69
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
70
|
+
nodeIntegration: false,
|
|
71
|
+
contextIsolation: true,
|
|
72
|
+
},
|
|
73
|
+
skipTaskbar: true,
|
|
74
|
+
resizable: true,
|
|
75
|
+
hasShadow: false
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
mainWindow.loadFile(path.join(__dirname, '../ui/index.html'));
|
|
79
|
+
|
|
80
|
+
// Create Context Menu (Right-Click)
|
|
81
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
82
|
+
{ label: 'Echo AI Agent', enabled: false },
|
|
83
|
+
{ type: 'separator' },
|
|
84
|
+
{
|
|
85
|
+
label: 'Hide (Ctrl+Shift+E)',
|
|
86
|
+
click: () => mainWindow.hide()
|
|
87
|
+
},
|
|
88
|
+
{ type: 'separator' },
|
|
89
|
+
{
|
|
90
|
+
label: 'Quit Echo',
|
|
91
|
+
click: () => app.quit()
|
|
92
|
+
}
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Attach context menu to window
|
|
96
|
+
mainWindow.webContents.on('context-menu', (e, params) => {
|
|
97
|
+
contextMenu.popup(mainWindow, params.x, params.y);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Register global hotkey
|
|
101
|
+
const hotkey = config.get('hotkey') || 'CommandOrControl+Shift+E';
|
|
102
|
+
globalShortcut.register(hotkey, () => {
|
|
103
|
+
if (mainWindow) {
|
|
104
|
+
if (mainWindow.isVisible()) {
|
|
105
|
+
mainWindow.hide();
|
|
106
|
+
} else {
|
|
107
|
+
mainWindow.show();
|
|
108
|
+
mainWindow.focus();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Send theme configuration to renderer
|
|
114
|
+
mainWindow.webContents.on('did-finish-load', () => {
|
|
115
|
+
const theme = config.get('theme') || 'cyan';
|
|
116
|
+
const themeColors = config.getThemeColors(theme);
|
|
117
|
+
mainWindow.webContents.send('apply-theme', themeColors);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Global error handlers
|
|
122
|
+
process.on('uncaughtException', (error) => {
|
|
123
|
+
console.error('Critical Error:', error);
|
|
124
|
+
// Optionally show a dialog to the user
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
128
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Initialize system
|
|
132
|
+
app.whenReady().then(async () => {
|
|
133
|
+
await initializeBrain();
|
|
134
|
+
createWindow();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.on('will-quit', () => {
|
|
138
|
+
// Unregister all shortcuts
|
|
139
|
+
globalShortcut.unregisterAll();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Handle voice/text commands from the UI
|
|
143
|
+
ipcMain.handle('process-input', async (event, text) => {
|
|
144
|
+
if (!brain) return { success: false, text: "API Key missing. Please run 'echo setup' first." };
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await brain.processCommand(text);
|
|
148
|
+
|
|
149
|
+
if (response.type === 'action') {
|
|
150
|
+
// Execute the system action
|
|
151
|
+
let result;
|
|
152
|
+
const cmd = response.command.toLowerCase();
|
|
153
|
+
const argsStr = Array.isArray(response.args) ? response.args.join(' ') : response.args;
|
|
154
|
+
|
|
155
|
+
// Enhanced command routing
|
|
156
|
+
if (cmd.includes('chrome') || cmd.includes('search') || cmd.includes('web')) {
|
|
157
|
+
result = await SystemActions.searchWeb(argsStr || text);
|
|
158
|
+
} else if (cmd.includes('mkdir') || cmd.includes('folder')) {
|
|
159
|
+
result = await SystemActions.createFolder(argsStr || "New Folder");
|
|
160
|
+
} else if (cmd.includes('screenshot')) {
|
|
161
|
+
result = await SystemActions.takeScreenshot();
|
|
162
|
+
} else if (cmd.includes('system') || cmd.includes('info')) {
|
|
163
|
+
result = SystemActions.getSystemInfo();
|
|
164
|
+
} else if (cmd.includes('time') || cmd.includes('date')) {
|
|
165
|
+
result = SystemActions.getDateTime();
|
|
166
|
+
} else if (cmd.includes('list') || cmd.includes('files')) {
|
|
167
|
+
result = await SystemActions.listFiles(argsStr);
|
|
168
|
+
} else if (cmd.includes('copy')) {
|
|
169
|
+
const [source, dest] = argsStr.split(' to ');
|
|
170
|
+
result = await SystemActions.copyFile(source, dest);
|
|
171
|
+
} else if (cmd.includes('delete')) {
|
|
172
|
+
result = await SystemActions.deleteFile(argsStr);
|
|
173
|
+
} else if (cmd.includes('url') || cmd.includes('open')) {
|
|
174
|
+
result = await SystemActions.openUrl(argsStr);
|
|
175
|
+
} else {
|
|
176
|
+
result = await SystemActions.openApp(cmd);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { success: true, text: response.text || "Action completed, sir.", action: response.command };
|
|
180
|
+
|
|
181
|
+
} else if (response.type === 'plugin_action') {
|
|
182
|
+
// Execute plugin command
|
|
183
|
+
const result = await pluginManager.executeCommand(response.command, response.args);
|
|
184
|
+
|
|
185
|
+
if (result.success) {
|
|
186
|
+
return { success: true, text: result.result.message || "Plugin executed successfully.", action: response.command };
|
|
187
|
+
} else {
|
|
188
|
+
return { success: false, text: `Plugin error: ${result.error}` };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { success: true, text: response.text };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error("Gemini Error:", error);
|
|
195
|
+
return { success: false, text: "I encountered an error processing that, sir." };
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Get configuration
|
|
200
|
+
ipcMain.handle('get-config', async () => {
|
|
201
|
+
return {
|
|
202
|
+
theme: config.get('theme') || 'cyan',
|
|
203
|
+
themeColors: config.getThemeColors(config.get('theme') || 'cyan')
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
|
package/main/preload.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { contextBridge, ipcRenderer } = require('electron');
|
|
2
|
+
|
|
3
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
4
|
+
executeCommand: (command, args) => ipcRenderer.invoke('system-command', { command, args }),
|
|
5
|
+
processInput: (text) => ipcRenderer.invoke('process-input', text),
|
|
6
|
+
onSpeechUpdate: (callback) => ipcRenderer.on('speech-update', callback),
|
|
7
|
+
getConfig: () => ipcRenderer.invoke('get-config'),
|
|
8
|
+
onApplyTheme: (callback) => ipcRenderer.on('apply-theme', (event, theme) => callback(theme))
|
|
9
|
+
});
|
|
10
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "echo-ai-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A premium JARVIS-inspired AI assistant for your desktop - voice-controlled, intelligent, and beautiful",
|
|
5
|
+
"main": "main/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"echo": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"preferGlobal": true,
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "electron .",
|
|
12
|
+
"dev": "electron . --inspect",
|
|
13
|
+
"postinstall": "node scripts/postinstall.js",
|
|
14
|
+
"uninstall": "node scripts/uninstall.js",
|
|
15
|
+
"prepublishOnly": "node scripts/prepublish.js",
|
|
16
|
+
"test": "echo \"Tests coming soon\" && exit 0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@google/generative-ai": "^0.21.0",
|
|
20
|
+
"chalk": "^4.1.2",
|
|
21
|
+
"commander": "^11.1.0",
|
|
22
|
+
"conf": "^10.2.0",
|
|
23
|
+
"dotenv": "^16.4.5",
|
|
24
|
+
"electron": "^32.0.0",
|
|
25
|
+
"inquirer": "^8.2.5",
|
|
26
|
+
"node-record-lpcm16": "^1.0.1",
|
|
27
|
+
"ora": "^5.4.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"ai",
|
|
32
|
+
"assistant",
|
|
33
|
+
"jarvis",
|
|
34
|
+
"voice-control",
|
|
35
|
+
"desktop-assistant",
|
|
36
|
+
"electron",
|
|
37
|
+
"gemini",
|
|
38
|
+
"productivity",
|
|
39
|
+
"automation",
|
|
40
|
+
"ai-agent",
|
|
41
|
+
"voice-assistant",
|
|
42
|
+
"system-control"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/MuhammadUsmanGM/Echo"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/MuhammadUsmanGM/Echo/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/MuhammadUsmanGM/Echo#readme",
|
|
52
|
+
"author": "Muhammad Usman",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=14.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Plugin for Echo AI Agent
|
|
3
|
+
*
|
|
4
|
+
* This is a sample plugin that demonstrates how to extend Echo with custom commands.
|
|
5
|
+
* Copy this file to create your own plugins!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
// Plugin metadata
|
|
10
|
+
name: 'example-plugin',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'An example plugin showing how to create custom commands',
|
|
13
|
+
author: 'Echo Team',
|
|
14
|
+
|
|
15
|
+
// Optional: Initialize plugin (runs once when loaded)
|
|
16
|
+
init: async function() {
|
|
17
|
+
console.log('Example plugin initialized!');
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// Command definitions
|
|
21
|
+
commands: {
|
|
22
|
+
/**
|
|
23
|
+
* Example command: greet
|
|
24
|
+
* Usage: "Echo, greet John"
|
|
25
|
+
*/
|
|
26
|
+
greet: async function(args) {
|
|
27
|
+
const name = args || 'there';
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
message: `Hello, ${name}! This is a custom command from the example plugin.`
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Example command: calculate
|
|
36
|
+
* Usage: "Echo, calculate 5 + 3"
|
|
37
|
+
*/
|
|
38
|
+
calculate: async function(args) {
|
|
39
|
+
try {
|
|
40
|
+
// Simple calculator (be careful with eval in production!)
|
|
41
|
+
const result = eval(args);
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
message: `The result is ${result}`
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
message: 'Invalid calculation'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Example command: joke
|
|
56
|
+
* Usage: "Echo, tell me a joke"
|
|
57
|
+
*/
|
|
58
|
+
joke: async function(args) {
|
|
59
|
+
const jokes = [
|
|
60
|
+
"Why don't scientists trust atoms? Because they make up everything!",
|
|
61
|
+
"Why did the scarecrow win an award? He was outstanding in his field!",
|
|
62
|
+
"Why don't eggs tell jokes? They'd crack each other up!",
|
|
63
|
+
"What do you call a bear with no teeth? A gummy bear!",
|
|
64
|
+
"Why did the bicycle fall over? It was two tired!"
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const randomJoke = jokes[Math.floor(Math.random() * jokes.length)];
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
message: randomJoke
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Example command: weather (mock)
|
|
77
|
+
* Usage: "Echo, what's the weather"
|
|
78
|
+
*/
|
|
79
|
+
weather: async function(args) {
|
|
80
|
+
// In a real plugin, you'd call a weather API here
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
message: 'The weather is sunny with a chance of code! 🌞 (This is a mock response)'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Optional: Command descriptions for help
|
|
89
|
+
commandDescriptions: {
|
|
90
|
+
greet: 'Greet someone by name',
|
|
91
|
+
calculate: 'Perform a simple calculation',
|
|
92
|
+
joke: 'Tell a random joke',
|
|
93
|
+
weather: 'Get weather information (mock)'
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Optional: Cleanup when plugin is disabled
|
|
97
|
+
cleanup: async function() {
|
|
98
|
+
console.log('Example plugin cleaned up!');
|
|
99
|
+
}
|
|
100
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const Conf = require('conf');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ConfigManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.config = new Conf({
|
|
7
|
+
projectName: 'echo-ai-agent',
|
|
8
|
+
defaults: {
|
|
9
|
+
configured: false,
|
|
10
|
+
theme: 'cyan',
|
|
11
|
+
position: 'bottom-right',
|
|
12
|
+
size: 'medium',
|
|
13
|
+
apiKey: null,
|
|
14
|
+
voice: 'auto',
|
|
15
|
+
hotkey: 'CommandOrControl+Shift+E',
|
|
16
|
+
alwaysOnTop: true,
|
|
17
|
+
startOnBoot: false,
|
|
18
|
+
plugins: []
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key) {
|
|
24
|
+
return this.config.get(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set(key, value) {
|
|
28
|
+
this.config.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(key) {
|
|
32
|
+
return this.config.has(key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
delete(key) {
|
|
36
|
+
this.config.delete(key);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
clear() {
|
|
40
|
+
this.config.clear();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get store() {
|
|
44
|
+
return this.config.store;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getThemeColors(themeName) {
|
|
48
|
+
const themes = {
|
|
49
|
+
cyan: { core: '#00f2ff', glow: 'rgba(0, 242, 255, 0.5)' },
|
|
50
|
+
purple: { core: '#a855f7', glow: 'rgba(168, 85, 247, 0.5)' },
|
|
51
|
+
green: { core: '#00ff88', glow: 'rgba(0, 255, 136, 0.5)' },
|
|
52
|
+
gold: { core: '#ffd700', glow: 'rgba(255, 215, 0, 0.5)' },
|
|
53
|
+
red: { core: '#ff0055', glow: 'rgba(255, 0, 85, 0.5)' },
|
|
54
|
+
blue: { core: '#0088ff', glow: 'rgba(0, 136, 255, 0.5)' }
|
|
55
|
+
};
|
|
56
|
+
return themes[themeName] || themes.cyan;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getWindowPosition(position, screenSize, windowSize) {
|
|
60
|
+
const positions = {
|
|
61
|
+
'top-left': { x: 20, y: 20 },
|
|
62
|
+
'top-right': { x: screenSize.width - windowSize.width - 20, y: 20 },
|
|
63
|
+
'bottom-left': { x: 20, y: screenSize.height - windowSize.height - 20 },
|
|
64
|
+
'bottom-right': {
|
|
65
|
+
x: screenSize.width - windowSize.width - 20,
|
|
66
|
+
y: screenSize.height - windowSize.height - 20
|
|
67
|
+
},
|
|
68
|
+
'center': {
|
|
69
|
+
x: (screenSize.width - windowSize.width) / 2,
|
|
70
|
+
y: (screenSize.height - windowSize.height) / 2
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return positions[position] || positions['bottom-right'];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getWindowSize(size) {
|
|
77
|
+
const sizes = {
|
|
78
|
+
small: { width: 250, height: 350 },
|
|
79
|
+
medium: { width: 350, height: 450 },
|
|
80
|
+
large: { width: 450, height: 550 }
|
|
81
|
+
};
|
|
82
|
+
return sizes[size] || sizes.medium;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ConfigManager = require('./config-manager');
|
|
4
|
+
|
|
5
|
+
class PluginManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.config = new ConfigManager();
|
|
8
|
+
this.plugins = new Map();
|
|
9
|
+
this.pluginDir = path.join(__dirname, '..', 'plugins');
|
|
10
|
+
|
|
11
|
+
// Create plugins directory if it doesn't exist
|
|
12
|
+
if (!fs.existsSync(this.pluginDir)) {
|
|
13
|
+
fs.mkdirSync(this.pluginDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load all plugins from the plugins directory
|
|
19
|
+
*/
|
|
20
|
+
async loadPlugins() {
|
|
21
|
+
const enabledPlugins = this.config.get('plugins') || [];
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const files = fs.readdirSync(this.pluginDir);
|
|
25
|
+
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
if (file.endsWith('.js')) {
|
|
28
|
+
const pluginPath = path.join(this.pluginDir, file);
|
|
29
|
+
await this.loadPlugin(pluginPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`Loaded ${this.plugins.size} plugin(s)`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Error loading plugins:', error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load a single plugin
|
|
41
|
+
*/
|
|
42
|
+
async loadPlugin(pluginPath) {
|
|
43
|
+
try {
|
|
44
|
+
const plugin = require(pluginPath);
|
|
45
|
+
|
|
46
|
+
// Validate plugin structure
|
|
47
|
+
if (!plugin.name || !plugin.commands) {
|
|
48
|
+
throw new Error('Invalid plugin structure. Must have name and commands.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if plugin is enabled
|
|
52
|
+
const enabledPlugins = this.config.get('plugins') || [];
|
|
53
|
+
if (!enabledPlugins.includes(plugin.name)) {
|
|
54
|
+
console.log(`Plugin ${plugin.name} is disabled. Skipping...`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.plugins.set(plugin.name, plugin);
|
|
59
|
+
console.log(`Loaded plugin: ${plugin.name} v${plugin.version || '1.0.0'}`);
|
|
60
|
+
|
|
61
|
+
// Run plugin initialization if it exists
|
|
62
|
+
if (plugin.init && typeof plugin.init === 'function') {
|
|
63
|
+
await plugin.init();
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Error loading plugin ${pluginPath}:`, error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Execute a command from a plugin
|
|
72
|
+
*/
|
|
73
|
+
async executeCommand(commandName, args) {
|
|
74
|
+
for (const [pluginName, plugin] of this.plugins) {
|
|
75
|
+
if (plugin.commands[commandName]) {
|
|
76
|
+
try {
|
|
77
|
+
const result = await plugin.commands[commandName](args);
|
|
78
|
+
return { success: true, plugin: pluginName, result };
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
plugin: pluginName,
|
|
83
|
+
error: error.message
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { success: false, error: 'Command not found in any plugin' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all available commands from all plugins
|
|
94
|
+
*/
|
|
95
|
+
getAvailableCommands() {
|
|
96
|
+
const commands = [];
|
|
97
|
+
|
|
98
|
+
for (const [pluginName, plugin] of this.plugins) {
|
|
99
|
+
for (const commandName of Object.keys(plugin.commands)) {
|
|
100
|
+
commands.push({
|
|
101
|
+
plugin: pluginName,
|
|
102
|
+
command: commandName,
|
|
103
|
+
description: plugin.commandDescriptions?.[commandName] || 'No description'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return commands;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Enable a plugin
|
|
113
|
+
*/
|
|
114
|
+
enablePlugin(pluginName) {
|
|
115
|
+
const enabledPlugins = this.config.get('plugins') || [];
|
|
116
|
+
if (!enabledPlugins.includes(pluginName)) {
|
|
117
|
+
enabledPlugins.push(pluginName);
|
|
118
|
+
this.config.set('plugins', enabledPlugins);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Disable a plugin
|
|
126
|
+
*/
|
|
127
|
+
disablePlugin(pluginName) {
|
|
128
|
+
const enabledPlugins = this.config.get('plugins') || [];
|
|
129
|
+
const index = enabledPlugins.indexOf(pluginName);
|
|
130
|
+
if (index > -1) {
|
|
131
|
+
enabledPlugins.splice(index, 1);
|
|
132
|
+
this.config.set('plugins', enabledPlugins);
|
|
133
|
+
this.plugins.delete(pluginName);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* List all installed plugins
|
|
141
|
+
*/
|
|
142
|
+
listPlugins() {
|
|
143
|
+
const plugins = [];
|
|
144
|
+
const enabledPlugins = this.config.get('plugins') || [];
|
|
145
|
+
|
|
146
|
+
for (const [name, plugin] of this.plugins) {
|
|
147
|
+
plugins.push({
|
|
148
|
+
name,
|
|
149
|
+
version: plugin.version || '1.0.0',
|
|
150
|
+
description: plugin.description || 'No description',
|
|
151
|
+
enabled: enabledPlugins.includes(name),
|
|
152
|
+
commands: Object.keys(plugin.commands).length
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return plugins;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = PluginManager;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
function postInstall() {
|
|
4
|
+
console.log('\n');
|
|
5
|
+
console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════════╗'));
|
|
6
|
+
console.log(chalk.cyan('║ ║'));
|
|
7
|
+
console.log(chalk.cyan('║') + chalk.bold.white(' ECHO AI AGENT ') + chalk.cyan('║'));
|
|
8
|
+
console.log(chalk.cyan('║') + chalk.gray(' Premium Desktop Assistant v1.0.0 ') + chalk.cyan('║'));
|
|
9
|
+
console.log(chalk.cyan('║ ║'));
|
|
10
|
+
console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════════╝'));
|
|
11
|
+
console.log('\n');
|
|
12
|
+
|
|
13
|
+
console.log(chalk.green.bold(' ✓ Installation Successful!'));
|
|
14
|
+
console.log(chalk.gray(' Welcome to the future of desktop automation.'));
|
|
15
|
+
|
|
16
|
+
console.log('\n' + chalk.yellow.bold(' 👉 NEXT STEP: SETUP'));
|
|
17
|
+
console.log(chalk.white(' To configure your API key, theme, and startup preferences, run:'));
|
|
18
|
+
console.log('\n ' + chalk.bgCyan.black.bold(' echo setup ') + '\n');
|
|
19
|
+
|
|
20
|
+
console.log(chalk.white(' Then launch Echo with:'));
|
|
21
|
+
console.log(' ' + chalk.cyan('echo start') + '\n');
|
|
22
|
+
|
|
23
|
+
console.log(chalk.gray(' Need help? Run ') + chalk.white('echo --help') + '\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (require.main === module) {
|
|
27
|
+
postInstall();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = postInstall;
|