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