Version: 1.0
Date: December 2024
Project: GraphLinq Terminal Plugin Architecture
Phase: 1 - Core Plugin System Implementation
This PRD outlines the implementation of a comprehensive plugin system for the GraphLinq Terminal application. The system will allow third-party developers to extend terminal functionality through a standardized SDK and plugin architecture. Phase 1 focuses on establishing the core plugin infrastructure, basic APIs, and management interface.
- Create a robust plugin loading and management system
- Provide essential APIs for terminal interaction and UI integration
- Implement a user-friendly plugin management interface
- Establish security and isolation mechanisms
- Enable hot-loading and dynamic plugin management
- Plugin loading time < 500ms per plugin
- Zero crashes from plugin failures (proper isolation)
- Developer onboarding time < 30 minutes
- Support for at least 10 concurrent plugins without performance degradation
orion/
├── plugins/ # Plugin directory (runtime)
│ ├── docker-manager/ # Example plugin
│ │ ├── manifest.json # Plugin metadata
│ │ ├── index.js # Entry point
│ │ ├── components/ # React components
│ │ └── assets/ # Static assets
├── src/
│ ├── services/
│ │ ├── pluginManager.ts # Core plugin management
│ │ ├── pluginAPI.ts # SDK implementation
│ │ └── pluginSecurity.ts # Security & validation
│ ├── components/
│ │ ├── PluginManager/ # Plugin management UI
│ │ └── PluginContainer/ # Plugin rendering container
└── types/
└── plugin.d.ts # TypeScript definitions
- Responsibility: Load, unload, and manage plugin lifecycle
- Key Methods:
loadPlugin(pluginPath: string): Promise<Plugin>unloadPlugin(pluginId: string): voidgetLoadedPlugins(): Plugin[]validatePlugin(manifest: PluginManifest): boolean
- Responsibility: Provide standardized APIs to plugins
- Key APIs:
- Terminal API (execute commands, read output)
- UI API (register components, show notifications)
- File System API (read/write files, navigate directories)
- Event API (listen/emit events)
- Storage API (persistent plugin data)
- Responsibility: Validate and isolate plugins
- Key Features:
- Manifest validation
- Permission checking
- Resource monitoring
- Error boundary implementation
{
"name": "string", // Unique plugin identifier
"version": "string", // Semantic version
"displayName": "string", // Human-readable name
"description": "string", // Plugin description
"author": "string", // Author information
"entry": "string", // Entry point file
"permissions": ["string[]"], // Required permissions
"category": "string" // Plugin category
}{
"icon": "string", // Icon file path
"homepage": "string", // Plugin homepage URL
"repository": "string", // Source code repository
"dependencies": ["string[]"], // Plugin dependencies
"minAppVersion": "string", // Minimum app version
"maxAppVersion": "string", // Maximum app version
"keywords": ["string[]"], // Search keywords
"license": "string" // License type
}interface TerminalAPI {
// Execute commands
execute(command: string, options?: ExecuteOptions): Promise<CommandResult>
// Stream command output
executeStream(command: string, onData: (data: string) => void): Promise<void>
// Get current session info
getCurrentSession(): SessionInfo | null
// Listen to terminal events
onOutput(callback: (data: string) => void): void
onError(callback: (error: string) => void): void
onSessionChange(callback: (session: SessionInfo) => void): void
}interface UIAPI {
// Register sidebar panel
addSidebarPanel(config: SidebarPanelConfig): string
// Remove sidebar panel
removeSidebarPanel(panelId: string): void
// Show notifications
showNotification(message: string, type: 'info' | 'success' | 'warning' | 'error'): void
// Open modal dialog
openModal(component: React.ComponentType, props?: any): Promise<any>
// Add context menu items
addContextMenuItem(config: ContextMenuConfig): string
// Add toolbar button
addToolbarButton(config: ToolbarButtonConfig): string
}interface FileSystemAPI {
// Read file content
readFile(path: string): Promise<string>
// Write file content
writeFile(path: string, content: string): Promise<void>
// List directory contents
listDirectory(path: string): Promise<FileInfo[]>
// Check file/directory existence
exists(path: string): Promise<boolean>
// Get file stats
getStats(path: string): Promise<FileStats>
// Watch file changes
watchFile(path: string, callback: (event: FileEvent) => void): string
}interface EventAPI {
// Listen to events
on(event: string, callback: Function): void
// Emit events
emit(event: string, data?: any): void
// Remove event listener
off(event: string, callback: Function): void
// Listen once
once(event: string, callback: Function): void
}interface StorageAPI {
// Get stored value
get(key: string): Promise<any>
// Set stored value
set(key: string, value: any): Promise<void>
// Remove stored value
remove(key: string): Promise<void>
// Clear all stored values
clear(): Promise<void>
// Get all keys
keys(): Promise<string[]>
}interface Plugin {
// Plugin metadata
manifest: PluginManifest
// Lifecycle hooks
onLoad?(sdk: PluginSDK): Promise<void> | void
onUnload?(): Promise<void> | void
onServerConnect?(serverInfo: ServerInfo): Promise<void> | void
onServerDisconnect?(): Promise<void> | void
// UI components (optional)
components?: {
SidebarPanel?: React.ComponentType<any>
ContextMenu?: React.ComponentType<any>
Modal?: React.ComponentType<any>
ToolbarButton?: React.ComponentType<any>
}
// Custom commands (optional)
commands?: {
[commandName: string]: (args: any[], sdk: PluginSDK) => Promise<any>
}
}interface PluginSDK {
terminal: TerminalAPI
ui: UIAPI
filesystem: FileSystemAPI
events: EventAPI
storage: StorageAPI
// Plugin info
getPluginInfo(): PluginManifest
// App info
getAppVersion(): string
getAppInfo(): AppInfo
}Location: Accessible from main menu → "Plugin Manager"
Features:
- List all installed plugins with status (enabled/disabled)
- Install new plugins (drag & drop, file upload, URL)
- Enable/disable plugins without uninstalling
- Uninstall plugins
- View plugin details and permissions
- Check for plugin updates
- Plugin settings/configuration
- Grid/List toggle: Switch between grid and list view
- Search/Filter: Search by name, category, or keywords
- Sort options: By name, category, install date, last updated
- Status indicators: Enabled, disabled, error, updating
- Basic info: Name, version, author, description
- Permissions: List of required permissions
- Dependencies: Required plugins or app versions
- Statistics: Usage stats, performance metrics
- Actions: Enable/disable, uninstall, configure
- Drag & Drop zone: Drop plugin folders or ZIP files
- File picker: Browse and select plugin files
- URL installer: Install from GitHub or other URLs
- Progress indicator: Installation progress and status
type Permission =
| 'terminal.execute' // Execute terminal commands
| 'terminal.read' // Read terminal output
| 'filesystem.read' // Read files
| 'filesystem.write' // Write files
| 'network.request' // Make HTTP requests
| 'ui.sidebar' // Add sidebar panels
| 'ui.modal' // Show modal dialogs
| 'ui.notification' // Show notifications
| 'storage.local' // Local storage access
| 'system.clipboard' // Clipboard access- Manifest validation: Required fields, valid JSON structure
- File validation: Entry point exists, valid JavaScript
- Permission validation: Only request necessary permissions
- Size limits: Maximum plugin size (50MB)
- Code scanning: Basic security checks (no eval, limited Node.js APIs)
- Each plugin runs in its own error boundary
- Plugin errors don't crash the main application
- Error reporting and logging for debugging
- Graceful degradation when plugins fail
- Memory usage monitoring per plugin
- CPU usage limits for plugin operations
- Automatic cleanup on plugin unload
- Garbage collection for unused plugin resources
-
Plugin Manager Service
- Create
PluginManagerclass with loading/unloading logic - Implement plugin discovery and validation
- Add plugin lifecycle management
- Create
-
Plugin API Implementation
- Implement
TerminalAPIwith command execution - Create
UIAPIfor component registration - Build
FileSystemAPIwith secure file operations - Implement
EventAPIfor plugin communication - Create
StorageAPIwith isolated plugin storage
- Implement
-
Security Layer
- Add manifest validation
- Implement permission checking
- Create error boundaries for plugins
- Add resource monitoring
-
Plugin Manager Interface
- Create main Plugin Manager component
- Implement plugin list with grid/list views
- Add search and filtering functionality
- Create plugin details panel
-
Plugin Installation
- Implement drag & drop installation
- Add file picker for plugin selection
- Create installation progress indicators
- Add error handling for failed installations
-
Plugin Integration
- Create plugin container components
- Implement sidebar panel registration
- Add context menu integration
- Create notification system
-
Testing
- Unit tests for plugin manager
- Integration tests for plugin APIs
- Security testing for validation
- Performance testing with multiple plugins
-
Documentation
- Plugin development guide
- API reference documentation
- Example plugin implementations
- Troubleshooting guide
- Plugin manager can load plugins from
/pluginsdirectory - Plugins can execute terminal commands and receive output
- Plugins can register UI components (sidebar panels, context menus)
- Plugin management interface allows install/uninstall/enable/disable
- Plugin errors are isolated and don't crash the application
- Plugin permissions are validated and enforced
- Plugin loading time < 500ms per plugin
- No noticeable performance impact with 10+ plugins loaded
- Memory usage increase < 100MB with 10 plugins
- UI remains responsive during plugin operations
- Plugin manifest validation prevents malformed plugins
- Permission system restricts plugin capabilities
- File system access is sandboxed to safe directories
- Network requests can be monitored and limited
- Plugin Store: Centralized marketplace for plugin discovery
- Plugin Updates: Automatic update system with version management
- Plugin Dependencies: Support for plugin-to-plugin dependencies
- Advanced APIs: Database connections, SSH tunneling, custom protocols
- Plugin Themes: Allow plugins to provide custom themes
- Plugin CLI: Command-line tools for plugin development and testing
The original plugin API was causing terminal interference issues where:
- Plugins would capture SSH output permanently
- Users couldn't type in the terminal after opening plugin interfaces
- Multiple plugins could conflict with each other's listeners
Temporary Listener Pattern: Inspired by the AI Assistant implementation, the plugin API now uses:
- Scoped Command Execution: Each plugin command execution creates a temporary listener
- Automatic Cleanup: Listeners are automatically removed after command completion
- Timeout Management: Commands have configurable timeouts with proper cleanup
- Prompt Detection: Smart detection of command completion using prompt patterns
- Non-Blocking: Plugin operations don't interfere with normal terminal usage
- Enhanced
terminal.execute(): Now uses temporary listeners like AI service - Improved
terminal.executeStream(): Proper cleanup and timeout handling - Smart Output Parsing: Detects command completion using multiple prompt patterns
- Error Handling: Better error messages and cleanup on failures
- Updated Command Execution: Uses new API with proper timeout configuration
- Improved Error Handling: Shows detailed error messages from command results
- Better Output Parsing: Uses Docker's
--formatoption for consistent parsing - Non-Blocking Operations: Plugin actions don't interfere with terminal input
// Before (Problematic)
window.electronAPI.onSSHData(permanentListener) // Never removed!
// After (Fixed)
const temporaryListener = (sessionId, data) => { /* handle data */ }
window.electronAPI.onSSHData(temporaryListener)
// ... command execution ...
window.electronAPI.offSSHData() // Properly cleaned up- No Terminal Interference: Users can type normally while plugins are active
- Better Resource Management: No memory leaks from permanent listeners
- Improved Reliability: Commands complete properly with timeout handling
- Enhanced User Experience: Plugin operations are transparent to users
- Scalable Architecture: Multiple plugins can coexist without conflicts
- Connection Stability: Plugins wait for SSH sessions to stabilize before executing commands
Problem: Plugins were trying to execute commands immediately after SSH connection, causing timeouts because the session wasn't fully established.
Solution: Added progressive delays:
- App Level: 2-second delay before notifying plugins of server connection
- Plugin Level: Additional 1-second delay before checking service availability
- Existing Sessions: 1.5-second delay when plugin loads with active session
- Robust Detection: Multiple fallback methods for service detection
// App.tsx - Delayed plugin notification
setTimeout(async () => {
await pluginManager.onServerConnect(sessionInfo)
}, 2000)
// Plugin - Additional stabilization delay
setTimeout(async () => {
await this.checkDockerAvailability(this.sdk)
}, 1000)Problem: Fast commands (like docker ps) were waiting for full timeout instead of detecting immediate completion.
Solution: Multi-level command completion detection:
- Immediate Detection: 10ms response for obvious prompt patterns in real-time data
- Quick Detection: 30ms response for fast commands with clear output
- Standard Detection: 50ms response for normal prompt patterns
- Inactivity Timeout: Reduced from 10s to 3s for faster fallback
// Ultra-fast detection for immediate prompts
if (lastRecentLine.match(/[@#$>]\s*$/)) {
setTimeout(() => resolve(cleanOutput()), 10)
}
// Quick detection for fast commands
if (lastLine.match(/[@#$>]\s*$/) && lines.length > 2) {
setTimeout(() => resolve(cleanOutput()), 30)
}Result: Docker commands now complete in ~100-500ms instead of 3-10 seconds!
Problem: After plugin command execution, users couldn't type in the terminal anymore because offSSHData() was removing ALL SSH listeners.
Root Cause: The plugin API was calling window.electronAPI.offSSHData() which removes all SSH data listeners, including those needed for normal terminal operation.
Solution: Remove offSSHData() calls and use only the isListening flag pattern:
// Before (Problematic)
const cleanup = () => {
isListening = false
window.electronAPI.offSSHData() // ❌ Removes ALL listeners!
}
// After (Fixed)
const cleanup = () => {
isListening = false
// ✅ Just stop listening, don't remove other listeners
}Pattern Used: Same as AI Service - listeners naturally filter themselves using the isListening flag and session ID checks.
Result: Terminal remains fully functional after plugin command execution!
Document Status: Draft
Next Review: After Phase 1 implementation
Stakeholders: Development Team, Plugin Developers, End Users