1pub mod error;
2pub mod manifest;
3pub mod permissions;
4pub mod types;
5
6pub use error::PluginError;
7pub use manifest::{CapabilityLevel, HostCapability, IntegrationConfig, PluginManifest};
8pub use permissions::Permission;
9pub use types::*;
10
11pub const PLUGIN_ABI_VERSION: u32 = 1;
14pub const PUBLIC_HOST_API_VERSION: &str = "0.1.0";
15
16pub trait HaloForgePlugin: Send + Sync {
24 fn metadata(&self) -> PluginMetadata;
26
27 fn on_load(
30 &mut self,
31 ctx: &dyn PluginContext,
32 ipc: &mut dyn IpcRegistrar,
33 ) -> Result<(), PluginError>;
34
35 fn on_unload(&mut self) -> Result<(), PluginError>;
38
39 fn on_settings_changed(&mut self, _settings: serde_json::Value) -> Result<(), PluginError> {
41 Ok(())
42 }
43
44 fn execute_workflow_step(
47 &mut self,
48 _step_type: &str,
49 _config: serde_json::Value,
50 _ctx: &dyn PluginContext,
51 ) -> Result<serde_json::Value, PluginError> {
52 Err(PluginError::Unsupported("execute_workflow_step".into()))
53 }
54}
55
56pub trait PluginContext: Send + Sync {
61 fn db(&self) -> &dyn DatabaseAccess;
63
64 fn events(&self) -> &dyn EventBus;
66
67 fn http(&self) -> Option<&dyn HttpClient>;
69
70 fn fs(&self) -> Option<&dyn PluginFs>;
72
73 fn process(&self) -> Option<&dyn ProcessRunner>;
75
76 fn settings(&self) -> serde_json::Value;
78
79 fn save_settings(&self, settings: serde_json::Value) -> Result<(), PluginError>;
81
82 fn data_dir(&self) -> std::path::PathBuf;
85
86 fn log(&self, level: LogLevel, msg: &str);
88
89 fn notify(&self, notification: Notification);
91}
92
93pub trait DatabaseAccess: Send + Sync {
96 fn query(
99 &self,
100 sql: &str,
101 params: &[serde_json::Value],
102 ) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>, PluginError>;
103
104 fn execute(
106 &self,
107 sql: &str,
108 params: &[serde_json::Value],
109 ) -> Result<usize, PluginError>;
110
111 fn create_table(&self, table_name: &str, schema_sql: &str) -> Result<(), PluginError>;
114
115 fn read_host_table(
118 &self,
119 table: HostTable,
120 limit: Option<u32>,
121 ) -> Result<Vec<serde_json::Value>, PluginError>;
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
126#[serde(rename_all = "snake_case")]
127pub enum HostTable {
128 LaunchProfiles,
129 Workflows,
130 CodeSnippets,
131 Skills,
132 McpServers,
133 ChatSessions,
134 ModelConfigs,
135}
136
137impl HostTable {
138 pub fn as_str(&self) -> &'static str {
139 match self {
140 Self::LaunchProfiles => "launch_profiles",
141 Self::Workflows => "workflows",
142 Self::CodeSnippets => "code_snippets",
143 Self::Skills => "skills",
144 Self::McpServers => "mcp_servers",
145 Self::ChatSessions => "chat_sessions",
146 Self::ModelConfigs => "model_configs",
147 }
148 }
149
150 pub fn required_permission(&self) -> Permission {
151 Permission::DatabaseRead(self.as_str().to_string())
152 }
153}
154
155pub trait EventBus: Send + Sync {
158 fn emit(&self, event: &str, payload: serde_json::Value) -> Result<(), PluginError>;
161
162 fn subscribe(
165 &self,
166 event: AppEvent,
167 handler: Box<dyn Fn(serde_json::Value) + Send + Sync>,
168 ) -> SubscriptionToken;
169
170 fn unsubscribe(&self, token: SubscriptionToken);
171}
172
173#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
175#[serde(tag = "type", rename_all = "snake_case")]
176pub enum AppEvent {
177 AppStarted,
178 AppShuttingDown,
179 ThemeChanged,
180 WorkflowStarted { workflow_id: String },
181 WorkflowCompleted { workflow_id: String, success: bool },
182 WorkflowStepCompleted { workflow_id: String, step_index: usize },
183 ProfileLaunched { profile_id: String },
184 ProfileStopped { profile_id: String },
185 ChatMessageSent { session_id: String },
186 ChatStreamCompleted{ session_id: String },
187 SettingsChanged,
188 Custom { name: String },
189}
190
191pub trait HttpClient: Send + Sync {
194 fn get(
195 &self,
196 url: &str,
197 headers: Option<std::collections::HashMap<String, String>>,
198 ) -> Result<HttpResponse, PluginError>;
199
200 fn post(
201 &self,
202 url: &str,
203 body: serde_json::Value,
204 headers: Option<std::collections::HashMap<String, String>>,
205 ) -> Result<HttpResponse, PluginError>;
206}
207
208#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
209pub struct HttpResponse {
210 pub status: u16,
211 pub headers: std::collections::HashMap<String, String>,
212 pub body: serde_json::Value,
213}
214
215pub trait PluginFs: Send + Sync {
218 fn read_file(&self, path: &std::path::Path) -> Result<Vec<u8>, PluginError>;
219 fn write_file(&self, path: &std::path::Path, content: &[u8]) -> Result<(), PluginError>;
220 fn read_dir(&self, path: &std::path::Path) -> Result<Vec<FsEntry>, PluginError>;
221 fn exists(&self, path: &std::path::Path) -> bool;
222 fn create_dir_all(&self, path: &std::path::Path) -> Result<(), PluginError>;
223 fn remove_file(&self, path: &std::path::Path) -> Result<(), PluginError>;
224 fn remove_dir_all(&self, path: &std::path::Path) -> Result<(), PluginError>;
225}
226
227#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
228pub struct FsEntry {
229 pub path: std::path::PathBuf,
230 pub is_dir: bool,
231 pub size: Option<u64>,
232}
233
234pub trait ProcessRunner: Send + Sync {
237 fn run(
239 &self,
240 executable: &str,
241 args: &[&str],
242 cwd: Option<&std::path::Path>,
243 ) -> Result<ProcessOutput, PluginError>;
244}
245
246#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
247pub struct ProcessOutput {
248 pub exit_code: i32,
249 pub stdout: String,
250 pub stderr: String,
251}
252
253pub type IpcHandler = Box<
258 dyn Fn(serde_json::Value, &dyn PluginContext) -> Result<serde_json::Value, PluginError>
259 + Send
260 + Sync,
261>;
262
263pub trait IpcRegistrar: Send + Sync {
264 fn register(&mut self, name: &str, handler: IpcHandler) -> Result<(), PluginError>;
267
268 fn register_workflow_step_type(
270 &mut self,
271 definition: WorkflowStepTypeDefinition,
272 ) -> Result<(), PluginError>;
273}
274
275#[macro_export]
284macro_rules! declare_plugin {
285 ($plugin_type:ty, $constructor:path) => {
286 #[no_mangle]
287 pub extern "C" fn _haloforge_plugin_create() -> *mut dyn $crate::HaloForgePlugin {
288 let plugin: $plugin_type = $constructor();
289 Box::into_raw(Box::new(plugin))
290 }
291
292 #[no_mangle]
293 pub extern "C" fn _haloforge_plugin_destroy(ptr: *mut dyn $crate::HaloForgePlugin) {
294 if !ptr.is_null() {
295 unsafe { drop(Box::from_raw(ptr)); }
296 }
297 }
298
299 #[no_mangle]
300 pub extern "C" fn _haloforge_abi_version() -> u32 {
301 $crate::PLUGIN_ABI_VERSION
302 }
303 };
304}