Skip to main content

haloforge_plugin_api/
manifest.rs

1use serde::{Deserialize, Serialize};
2use crate::permissions::Permission;
3
4/// Full parsed plugin manifest (from manifest.json inside .hfpkg).
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct PluginManifest {
7    pub id: String,
8    pub name: String,
9    pub version: String,
10    pub description: String,
11    #[serde(default)]
12    pub long_description: Option<String>,
13    pub author: String,
14    #[serde(default)]
15    pub author_url: Option<String>,
16    #[serde(default)]
17    pub homepage: Option<String>,
18    #[serde(default)]
19    pub license: Option<String>,
20    #[serde(default)]
21    pub keywords: Vec<String>,
22    #[serde(default)]
23    pub icon: Option<String>,
24
25    pub compatibility: CompatibilitySpec,
26
27    /// Which capability levels this plugin uses (e.g. [1, 4]).
28    pub capability_levels: Vec<CapabilityLevel>,
29
30    /// Per-level integration configuration.
31    #[serde(default)]
32    pub integration: IntegrationConfig,
33
34    /// Entry points for native library and frontend bundle.
35    #[serde(default)]
36    pub entry: EntryConfig,
37
38    /// Other plugin IDs this plugin depends on.
39    #[serde(default)]
40    pub dependencies: Vec<PluginDependency>,
41
42    /// Declared permissions (checked at install time and enforced at runtime).
43    #[serde(default)]
44    pub permissions: Vec<Permission>,
45
46    /// JSON Schema for plugin settings (auto-rendered in Plugin Manager).
47    #[serde(default)]
48    pub settings_schema: Option<serde_json::Value>,
49
50    /// IPC commands this plugin registers (informational, for documentation).
51    #[serde(default)]
52    pub commands: Vec<CommandDeclaration>,
53
54    /// SHA-256 checksum of the .hfpkg file. Required for published plugins.
55    #[serde(default)]
56    pub checksum: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct CompatibilitySpec {
61    pub min_app_version: String,
62    #[serde(default)]
63    pub max_app_version: Option<String>,
64    #[serde(default = "all_platforms")]
65    pub platforms: Vec<String>,
66}
67
68fn all_platforms() -> Vec<String> {
69    vec!["windows".into(), "macos".into(), "linux".into()]
70}
71
72/// Capability level integer constants (matching the design doc).
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(from = "u8", into = "u8")]
75pub enum CapabilityLevel {
76    /// Level 0 — Top-level module (same tier as DevKit/AIChat).
77    Module = 0,
78    /// Level 1 — Feature inside an existing module.
79    ModuleFeature = 1,
80    /// Level 2 — UI slot injection / extension.
81    UiExtension = 2,
82    /// Level 3 — AI assistant registration.
83    AiAssistant = 3,
84    /// Level 4 — Headless service / backend extension.
85    Service = 4,
86}
87
88impl From<u8> for CapabilityLevel {
89    fn from(v: u8) -> Self {
90        match v {
91            0 => Self::Module,
92            1 => Self::ModuleFeature,
93            2 => Self::UiExtension,
94            3 => Self::AiAssistant,
95            4 => Self::Service,
96            _ => Self::Service,
97        }
98    }
99}
100
101impl From<CapabilityLevel> for u8 {
102    fn from(l: CapabilityLevel) -> u8 {
103        l as u8
104    }
105}
106
107/// Integration configuration block — one sub-block per declared level.
108#[derive(Debug, Clone, Default, Serialize, Deserialize)]
109pub struct IntegrationConfig {
110    #[serde(default)]
111    pub level0: Option<Level0Config>,
112    #[serde(default)]
113    pub level1: Option<Level1Config>,
114    #[serde(default)]
115    pub level2: Option<Level2Config>,
116    #[serde(default)]
117    pub level3: Option<Level3Config>,
118    #[serde(default)]
119    pub level4: Option<Level4Config>,
120}
121
122/// Level 0 — The plugin adds a new top-level module to the sidebar.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct Level0Config {
125    /// Unique module ID (must not collide with "devkit", "aichat", "settings").
126    pub module_id: String,
127    pub module_label: String,
128    /// Lucide icon name.
129    pub module_icon: String,
130    /// "main" = above the settings divider; "bottom" = below it.
131    #[serde(default = "default_sidebar_position")]
132    pub sidebar_position: String,
133    /// Lower = higher up. Defaults to 100.
134    #[serde(default = "default_sidebar_order")]
135    pub sidebar_order: u32,
136    /// Path inside the package to the JS bundle for this module's panel.
137    pub panel_entry: String,
138}
139
140fn default_sidebar_position() -> String { "main".into() }
141fn default_sidebar_order() -> u32 { 100 }
142
143/// Level 1 — The plugin adds a feature tab to an existing module.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Level1Config {
146    /// Target module: "devkit", "aichat", or a plugin module_id.
147    pub parent_module: String,
148    /// Unique tab ID within the parent module.
149    pub tab_id: String,
150    pub tab_label: String,
151    /// Lucide icon name.
152    pub tab_icon: String,
153    /// "after:snippet" | "before:summary" | "index:5"
154    #[serde(default)]
155    pub tab_position: Option<String>,
156    /// Path inside the package to the JS bundle for this tab's panel.
157    pub panel_entry: String,
158}
159
160/// Level 2 — The plugin injects into UI slots.
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Level2Config {
163    /// Which slots the plugin injects into (see UI Slot Reference in the design doc).
164    pub slots: Vec<String>,
165}
166
167/// Level 3 — The plugin registers an AI assistant.
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Level3Config {
170    pub assistant_id: String,
171    pub assistant_name: String,
172    #[serde(default)]
173    pub assistant_icon: Option<String>,
174    #[serde(default)]
175    pub assistant_description: Option<String>,
176    /// Path inside the package to the system prompt markdown file.
177    pub system_prompt_file: String,
178    /// Optional: auto-select a specific model_config_id for this assistant.
179    #[serde(default)]
180    pub preferred_model: Option<String>,
181}
182
183/// Level 4 — The plugin registers backend services / workflow step types.
184#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185pub struct Level4Config {
186    /// Step type IDs this plugin registers (e.g. ["p4_sync", "p4_submit"]).
187    #[serde(default)]
188    pub workflow_step_types: Vec<String>,
189}
190
191/// Native library paths per platform.
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct EntryConfig {
194    #[serde(default)]
195    pub native: Option<NativeEntry>,
196    #[serde(default)]
197    pub frontend: Option<String>,
198    #[serde(default)]
199    pub frontend_styles: Option<String>,
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct NativeEntry {
204    #[serde(default)]
205    pub macos_arm64: Option<String>,
206    #[serde(default)]
207    pub macos_x64: Option<String>,
208    #[serde(default)]
209    pub windows_x64: Option<String>,
210    #[serde(default)]
211    pub windows_arm64: Option<String>,
212    #[serde(default)]
213    pub linux_x64: Option<String>,
214    #[serde(default)]
215    pub linux_arm64: Option<String>,
216}
217
218impl NativeEntry {
219    /// Return the library path for the current platform/arch, if present.
220    pub fn for_current_platform(&self) -> Option<&str> {
221        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
222        return self.macos_arm64.as_deref();
223
224        #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
225        return self.macos_x64.as_deref();
226
227        #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
228        return self.windows_x64.as_deref();
229
230        #[cfg(all(target_os = "windows", target_arch = "aarch64"))]
231        return self.windows_arm64.as_deref();
232
233        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
234        return self.linux_x64.as_deref();
235
236        #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
237        return self.linux_arm64.as_deref();
238
239        #[allow(unreachable_code)]
240        None
241    }
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct PluginDependency {
246    pub id: String,
247    /// SemVer requirement string, e.g. ">=1.0.0".
248    pub version: String,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct CommandDeclaration {
253    pub id: String,
254    #[serde(default)]
255    pub description: Option<String>,
256}