Aidy is an AI-powered desktop launcher and assistant built with Electron, React, and TypeScript. It provides quick access to AI capabilities through a Spotlight-like interface with an extensible plugin system.
- Global Hotkey Access: Summon Aidy from anywhere with Alt+Space (customizable)
- LLM Integration: Powered by LM Studio for local AI inference
- Plugin System: Extensible architecture for adding custom functionality
- Tool Calling: LLM can invoke tools exposed by plugins
- Clean UI: Modern React-based interface with search and results display
- System Tray: Runs quietly in the background
- Cross-Platform: Works on Windows, macOS, and Linux
- Local Database: SQLite-based storage for settings and plugin state
- Node.js (v16 or higher)
- npm (comes with Node.js)
- LM Studio running at
http://localhost:1234- REQUIRED for all functionality- Download from lmstudio.ai
- Load a model that supports tool calling (function calling)
- Start the local server
- Without LM Studio, Aidy cannot function
-
Clone the repository
git clone <repository-url> cd Aidy
-
Install dependencies
npm install
-
Start LM Studio
- Open LM Studio
- Load your preferred model
- Start the local server (default: http://localhost:1234)
-
Run in development mode
npm run dev
Build for your current platform:
npm run distPlatform-specific builds:
npm run dist:win # Windows (portable)
npm run dist:mac # macOS (dmg + zip)
npm run dist:linux # Linux (AppImage + deb)Built applications will be in the build-output/ directory.
- Global Hotkey: Press
Alt+Space(default) from anywhere - System Tray: Click the Aidy icon in your system tray
- Direct Launch: Open the application from your applications folder
- Search/Ask: Type your query in the search bar
- LLM Processing: The LLM analyzes your query and decides which plugin tool to call
- Tool Execution: The appropriate plugin tool is invoked automatically
- View Results: Results are displayed with custom UI from the plugin
- Access Settings: Click the settings icon in the footer
- Manage Plugins: Click the grid icon to view/manage plugins
- Close: Press
Escor click the close button
How It Works: All user queries go through the LLM Chat plugin, which uses the LLM to determine the best tool to call based on plugin descriptions. The LLM makes tool calls like get_weather, search_notes, etc., and plugins respond through their tool providers.
- "What is the capital of France?"
- "How do I reverse a string in Python?"
- "Explain quantum computing in simple terms"
- Any general knowledge or coding question
Aidy/
├── src/
│ ├── main/ # Electron main process
│ │ ├── index.ts # Application entry point
│ │ ├── window.ts # Window management
│ │ ├── hotkey.ts # Global hotkey handling
│ │ ├── tray.ts # System tray integration
│ │ ├── splash.ts # Splash screen
│ │ └── ipc-handlers.ts # IPC communication
│ │
│ ├── renderer/ # React renderer process
│ │ ├── App.tsx # Main React component
│ │ ├── SearchBar.tsx # Search input component
│ │ ├── Results.tsx # Results display
│ │ ├── PluginManager.tsx # Plugin management UI
│ │ └── Settings.tsx # Settings UI
│ │
│ ├── core/ # Core functionality
│ │ ├── database/ # SQLite database
│ │ ├── llm/ # LM Studio client
│ │ └── plugin-system/ # Plugin architecture
│ │ ├── BasePlugin.ts
│ │ ├── PluginInterface.ts
│ │ ├── PluginLoader.ts
│ │ ├── PluginRegistry.ts
│ │ ├── ToolRegistry.ts
│ │ └── LLMRouter.ts
│ │
│ └── plugins/ # Built-in plugins
│ └── llm-chat/ # LLM Chat plugin
│
├── assets/ # Application assets (icons, etc.)
├── dist/ # Compiled output
├── build-output/ # Distribution packages
├── package.json # Dependencies and scripts
├── electron-builder.json # Build configuration
├── tsconfig.json # TypeScript configuration
└── vite.config.ts # Vite configuration
The main process (src/main/) handles:
- Application Lifecycle: Startup, shutdown, single instance lock
- Window Management: Creating and controlling the UI window
- Global Hotkeys: Registering system-wide keyboard shortcuts
- System Tray: Background operation and tray menu
- Plugin System: Loading, compiling, and managing plugins
- Database: Persistent storage using SQLite
- IPC Handlers: Communication bridge with renderer
The renderer process (src/renderer/) provides:
- Search Interface: Input and query handling
- Results Display: Dynamic rendering based on plugin output
- Plugin Management: UI for enabling/disabling plugins
- Settings Panel: Application and LLM configuration
- Plugin UIs: Custom React components from plugins
- SQLite database stored in user data directory
- Tables for plugin state and app settings
- Helper methods for plugin and settings management
- Communicates with LM Studio's OpenAI-compatible API
- Supports chat completions and tool calling
- Streaming responses for real-time output
- Configurable temperature and token limits
- PluginInterface: Defines plugin contract
- BasePlugin: Abstract base class for plugin development
- PluginLoader: Discovers and loads plugins from filesystem
- PluginRegistry: Manages loaded plugins and their state
- ToolRegistry: Manages LLM-callable tools from plugins
- LLM Chat Plugin: Core plugin that routes all queries through LLM tool calling
How Plugins Work: Aidy uses a centralized routing architecture with LLM tool calling:
- LLMRouter receives all user input and routes to the LLM Chat plugin
- LLM Chat plugin's execute() method is called with the user input
- Tool Discovery: Collects all tool definitions from enabled plugins via ToolRegistry
- LLM Tool Calling: Sends user query + tools to LLM, which decides which tool to call
- Tool Execution: The chosen plugin's
executeTool()method runs - Result Display: Tool returns data with optional
_displaymetadata for UI
Example Flow:
User: "What's the weather in Paris?"
→ LLMRouter → llm-chat.execute()
→ LLM receives: query + [get_weather_forecast, search_clipboard, google_search, ...]
→ LLM decides: call get_weather_forecast with args {location: "Paris"}
→ Weather plugin's executeTool() runs
→ Returns weather data + display structure
Plugins provide:
- Tool Providers: Define tools the LLM can call via
getToolDefinition()andexecuteTool() - execute() Method: Required by interface, but only llm-chat's is actually called
- Custom UI: React components for displaying results (optional)
- Persist Settings: Store configuration in the database
- Settings UI: Custom settings interface (optional)
Important Notes:
- Only the LLM Chat plugin's
execute()method is invoked in normal operation - Other plugins respond via their
toolProvider.executeTool()method - Tool names use snake_case (e.g.,
get_weather_forecast,search_clipboard_history)
npm run dev # Start development mode
npm run build # Build main, renderer, and plugins
npm run build:main # Compile main process
npm run build:renderer # Build renderer with Vite
npm run build:plugins # Compile plugins
npm run dist # Build and package app
npm start # Run built applicationIn development mode (npm run dev):
- Renderer auto-reloads on changes (via Vite)
- Plugin changes are detected and recompiled
- Main process requires manual restart
Plugins are stored in the user data directory under plugin-build/plugins/ and compiled to plugins-compiled/plugins/.
Important: Plugins work through LLM tool calling. Your plugin must provide a toolProvider that defines a tool for the LLM to call.
Example plugin with tool provider:
import { BasePlugin } from '../../core/plugin-system/BasePlugin';
import { PluginContext, PluginResult } from '../../core/plugin-system/PluginInterface';
import { LLMToolDefinition } from '../../core/llm/LMStudioClient';
export class MyPlugin extends BasePlugin {
constructor() {
super({
id: 'my-plugin',
name: 'My Plugin',
description: 'Performs a specific task when requested',
version: '1.0.0',
enabled: true
});
// Define tool provider for LLM
this.toolProvider = {
getToolDefinition: () => ({
type: 'function',
function: {
name: 'do_my_task',
description: 'Performs my specific task',
parameters: {
type: 'object',
properties: {
input: { type: 'string', description: 'Task input' }
},
required: ['input']
}
}
}),
executeTool: async (args, context) => {
// This runs when LLM calls the tool
return {
result: 'Task completed',
_display: {
pluginId: this.id,
data: { result: 'Task completed' },
display: {
type: 'text',
title: 'My Plugin',
content: `Task completed with input: ${args.input}`
},
actions: []
}
};
}
};
}
async execute(input: string, context: PluginContext): Promise<PluginResult> {
// Not used when called as tool
return this.helpers.textResult('Use via LLM tool calling', 'My Plugin');
}
}See PLUGIN_DEVELOPMENT.md for comprehensive documentation.
Settings are stored in:
- Windows:
%APPDATA%/Aidy/ - macOS:
~/Library/Application Support/Aidy/ - Linux:
~/.config/Aidy/
aidy.db- SQLite database with settings and plugin stateaidy.log- Application logs
Via the Settings UI:
- LM Studio URL: Default
http://localhost:1234/v1 - Global Hotkey: Default
Alt+Space - Temperature: LLM sampling temperature
- Max Tokens: Maximum response length
- Base Prompt: System prompt for the LLM
- ID:
llm-chat - Purpose: Core routing plugin that handles ALL user queries
- How It Works:
- Receives user input
- Collects tool definitions from all enabled plugins
- Sends query + tools to LLM
- LLM decides which tool to call (e.g.,
get_weather_forecast) - Routes tool call to appropriate plugin's
executeTool()method - Returns result to user
- Features:
- LLM-based tool routing
- Direct LLM responses (when no tool is called)
- Tool calling orchestration
- Thinking/reasoning display
- Markdown rendering
- Note: This plugin cannot be disabled as it is required for the application to function
- Ensure LM Studio is running at
http://localhost:1234 - Check that a model is loaded in LM Studio
- Verify the local server is started
- Check logs in the user data directory
CRITICAL: Your LLM model MUST support tool calling (function calling). Recommended models:
- Models with "tool" or "function" in their name
- Models specifically fine-tuned for function calling
- Check the model card to verify tool calling support
Without tool calling support, plugins will not work correctly and Aidy's core functionality will be limited.
- Check
aidy.login the user data directory - Verify plugin compilation succeeded
- Ensure TypeScript has no errors
- Check plugin is enabled in Plugin Manager
- Check another app isn't using the same hotkey
- Try changing the hotkey in Settings
- On Linux, ensure you have proper permissions
- Clear build artifacts:
rm -rf dist build-output - Reinstall dependencies:
rm -rf node_modules && npm install - Check Node.js version is 16+
- Electron: Desktop application framework
- React: UI library
- TypeScript: Type-safe JavaScript
- Vite: Frontend build tool
- better-sqlite3: SQLite database
- LM Studio: Local LLM inference
- react-markdown: Markdown rendering
- react-icons: Icon library
ISC
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
For issues, questions, or feature requests, please use the GitHub issue tracker.