# Pinakes Plugin Examples This directory contains example plugins demonstrating the Pinakes plugin system. ## Overview Pinakes supports extensibility through a WASM-based plugin system. Plugins can extend Pinakes functionality by: - **Media Type Providers**: Add support for new file formats - **Metadata Extractors**: Extract metadata from files - **Thumbnail Generators**: Generate thumbnails for media types - **Search Backends**: Implement custom search algorithms - **Event Handlers**: React to system events - **Theme Providers**: Provide custom UI themes ## Example Plugins ### 1. Markdown Metadata Extractor **Directory**: `markdown-metadata/` Enhances markdown file support with advanced frontmatter parsing. **Demonstrates**: - Metadata extraction from files - Plugin configuration via `plugin.toml` - Minimal capability requirements **Plugin Kind**: `metadata_extractor` ### 2. HEIF/HEIC Support **Directory**: `heif-support/` Adds support for HEIF and HEIC image formats. **Demonstrates**: - Media type registration - Metadata extraction from binary formats - Thumbnail generation - Resource limits (memory, CPU time) **Plugin Kinds**: `media_type`, `metadata_extractor`, `thumbnail_generator` ## Plugin Architecture ### Plugin Structure ``` my-plugin/ ├── plugin.toml # Plugin manifest ├── Cargo.toml # Rust project configuration ├── src/ │ └── lib.rs # Plugin implementation └── README.md # Plugin documentation ``` ### Plugin Manifest (plugin.toml) ```toml [plugin] name = "my-plugin" version = "1.0.0" api_version = "1.0" author = "Your Name" description = "Description of your plugin" kind = ["metadata_extractor"] [plugin.binary] wasm = "my_plugin.wasm" [capabilities] network = false [capabilities.filesystem] read = ["/path/to/read"] write = ["/path/to/write"] [config] # Plugin-specific configuration option1 = "value1" option2 = 42 ``` ### Manifest Fields #### [plugin] Section - `name`: Plugin identifier (must be unique) - `version`: Semantic version (e.g., "1.0.0") - `api_version`: Pinakes Plugin API version (currently "1.0") - `author`: Plugin author (optional) - `description`: Short description (optional) - `homepage`: Plugin homepage URL (optional) - `license`: License identifier (optional) - `kind`: Array of plugin kinds - `dependencies`: Array of plugin names this plugin depends on (optional) #### [plugin.binary] Section - `wasm`: Path to WASM binary (relative to manifest) - `entrypoint`: Custom entrypoint function name (optional, default: "_start") #### [capabilities] Section Capabilities define what the plugin can access: **Filesystem**: ```toml [capabilities.filesystem] read = ["/tmp/cache", "/var/data"] write = ["/tmp/output"] ``` **Network**: ```toml [capabilities] network = true # or false ``` **Environment**: ```toml [capabilities] environment = ["PATH", "HOME"] # or omit for no access ``` **Resource Limits**: ```toml [capabilities] max_memory_mb = 128 max_cpu_time_secs = 10 ``` ### Plugin Kinds #### media_type Register new media types with file extensions and MIME types. **Trait**: `MediaTypeProvider` **Methods**: - `supported_media_types()`: Returns list of media type definitions - `can_handle(path, mime_type)`: Check if plugin can handle a file #### metadata_extractor Extract metadata from files. **Trait**: `MetadataExtractor` **Methods**: - `extract_metadata(path)`: Extract metadata from file - `supported_types()`: Returns list of supported media type IDs #### thumbnail_generator Generate thumbnails for media files. **Trait**: `ThumbnailGenerator` **Methods**: - `generate_thumbnail(path, output_path, options)`: Generate thumbnail - `supported_types()`: Returns list of supported media type IDs #### search_backend Implement custom search algorithms. **Trait**: `SearchBackend` **Methods**: - `index_item(item)`: Index a media item - `remove_item(item_id)`: Remove item from index - `search(query)`: Perform search - `get_stats()`: Get index statistics #### event_handler React to system events. **Trait**: `EventHandler` **Methods**: - `handle_event(event)`: Handle an event - `interested_events()`: Returns list of event types to receive #### theme_provider Provide UI themes. **Trait**: `ThemeProvider` **Methods**: - `get_themes()`: List available themes - `load_theme(theme_id)`: Load theme data ## Creating a Plugin ### Step 1: Set Up Project ```bash # Create new Rust library project cargo new --lib my-plugin cd my-plugin # Add dependencies cat >> Cargo.toml <, } #[async_trait] impl Plugin for MyPlugin { fn metadata(&self) -> &PluginMetadata { // Return plugin metadata } async fn initialize(&mut self, context: PluginContext) -> PluginResult<()> { self.context = Some(context); Ok(()) } async fn shutdown(&mut self) -> PluginResult<()> { Ok(()) } async fn health_check(&self) -> PluginResult { Ok(HealthStatus { healthy: true, message: None, metrics: Default::default(), }) } } #[async_trait] impl MetadataExtractor for MyPlugin { async fn extract_metadata(&self, path: &PathBuf) -> PluginResult { // Extract metadata from file } fn supported_types(&self) -> Vec { vec!["my_type".to_string()] } } ``` ### Step 3: Build to WASM ```bash # Install WASM target rustup target add wasm32-wasi # Build cargo build --target wasm32-wasi --release # Optimize (optional, wasm-tools provides wasm-strip functionality) cargo install wasm-tools wasm-tools strip target/wasm32-wasi/release/my_plugin.wasm -o target/wasm32-wasi/release/my_plugin.wasm # Copy to plugin directory cp target/wasm32-wasi/release/my_plugin.wasm . ``` ### Step 4: Create Manifest Create `plugin.toml` with appropriate configuration (see examples above). ### Step 5: Install Plugin ```bash # Copy to plugins directory cp -r my-plugin ~/.config/pinakes/plugins/ # Or use API curl -X POST http://localhost:3000/api/v1/plugins/install \ -d '{"source": "/path/to/my-plugin"}' ``` ## Testing Plugins ### Unit Tests ```rust #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_metadata_extraction() { let mut plugin = MyPlugin::default(); let context = PluginContext { data_dir: PathBuf::from("/tmp/data"), cache_dir: PathBuf::from("/tmp/cache"), config: Default::default(), capabilities: Default::default(), }; plugin.initialize(context).await.unwrap(); let metadata = plugin .extract_metadata(&PathBuf::from("test.txt")) .await .unwrap(); assert!(metadata.title.is_some()); } } ``` ### Integration Tests ```bash # Load plugin in test Pinakes instance pinakes --config test-config.toml plugin load /path/to/plugin # Verify plugin is loaded pinakes plugin list # Test plugin functionality pinakes scan /path/to/test/files ``` ## Security Considerations ### Capability-Based Security Plugins operate in a sandbox with explicit capabilities. Only request the minimum capabilities needed: **Good**: ```toml [capabilities.filesystem] read = ["/tmp/cache"] ``` **Bad**: ```toml [capabilities.filesystem] read = ["/", "/home", "/etc"] # Too broad! ``` ### Resource Limits Always set appropriate resource limits: ```toml [capabilities] max_memory_mb = 128 # Reasonable for image processing max_cpu_time_secs = 10 # Prevent runaway operations ``` ### Input Validation Always validate input in your plugin: ```rust async fn extract_metadata(&self, path: &PathBuf) -> PluginResult { // Check file exists if !path.exists() { return Err(PluginError::InvalidInput("File not found".to_string())); } // Check file size let metadata = std::fs::metadata(path) .map_err(|e| PluginError::IoError(e.to_string()))?; if metadata.len() > 10_000_000 { // 10MB limit return Err(PluginError::InvalidInput("File too large".to_string())); } // Process file... } ``` ## Best Practices ### Error Handling - Use descriptive error messages - Return appropriate `PluginError` variants - Don't panic - return errors instead ### Performance - Avoid blocking operations in async functions - Use streaming for large files - Implement timeouts for external operations - Cache results when appropriate ### Configuration - Provide sensible defaults - Document all configuration options - Validate configuration during initialization ### Documentation - Write clear README with examples - Document all configuration options - Include troubleshooting section - Provide integration examples ## API Reference See the [pinakes-plugin-api documentation](../../crates/pinakes-plugin-api/README.md) for detailed API reference. ## Plugin Distribution ### Plugin Registry (Future) A centralized plugin registry is planned for future releases: ```bash # Install from registry pinakes plugin install markdown-metadata # Search plugins pinakes plugin search heif # Update all plugins pinakes plugin update --all ``` ### Manual Distribution Currently, plugins are distributed as directories containing: - `plugin.toml` manifest - WASM binary - README and documentation ## Troubleshooting ### Plugin Won't Load **Check manifest syntax**: ```bash # Validate TOML syntax taplo check plugin.toml ``` **Check API version**: Ensure `api_version = "1.0"` in manifest. **Check binary path**: Verify WASM binary exists at path specified in `plugin.binary.wasm`. ### Plugin Crashes **Check resource limits**: Increase `max_memory_mb` or `max_cpu_time_secs` if operations are timing out. **Check capabilities**: Ensure plugin has necessary filesystem/network capabilities. **Check logs**: ```bash # View plugin logs tail -f ~/.local/share/pinakes/logs/plugin.log ``` ### Permission Denied Errors **Filesystem capabilities**: Add required paths to `capabilities.filesystem.read` or `.write`. **Network capabilities**: Set `capabilities.network = true` if plugin needs network access. ## Support - **Issues**: https://github.com/notashelf/pinakes/issues - **Discussions**: https://github.com/notashelf/pinakes/discussions - **Documentation**: https://pinakes.readthedocs.io ## License All example plugins are licensed under MIT.