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