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/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
+
@@ -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;