diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 02:43:45 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 03:47:07 +0300 |
| commit | fbd20ad6e488fb3b9efc1612fdffee6e672976c1 (patch) | |
| tree | d21daf93241786f8aa6fde23b41cdb9e15182b79 /src/api/schema/plugins.rs | |
| parent | d35c642ef36ee5ac2afe7c1b18ad5a717bc807d8 (diff) | |
feat: add plugin v1 system
Add manifest-driven plugin actions, event hooks, pane entrypoints, link handlers, and host APIs.
Support plugin link, GitHub install, build commands, installed plugin registry, pane movement, runtime context, and plugin docs.
Diffstat (limited to 'src/api/schema/plugins.rs')
| -rw-r--r-- | src/api/schema/plugins.rs | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/api/schema/plugins.rs b/src/api/schema/plugins.rs new file mode 100644 index 00000000..535733ec --- /dev/null +++ b/src/api/schema/plugins.rs @@ -0,0 +1,343 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::common::AgentStatus; +use super::common::SplitDirection; +use super::panes::PaneInfo; +use super::workspaces::WorkspaceWorktreeInfo; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginLinkParams { + pub path: String, + #[serde(default = "super::common::default_true")] + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source: Option<PluginSourceInfo>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct PluginListParams { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plugin_id: Option<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginUnlinkParams { + pub plugin_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginSetEnabledParams { + pub plugin_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct InstalledPluginInfo { + pub plugin_id: String, + pub name: String, + pub version: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + pub manifest_path: String, + pub plugin_root: String, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub build: Vec<PluginManifestBuild>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub actions: Vec<PluginManifestAction>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub events: Vec<PluginManifestEventHook>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub panes: Vec<PluginManifestPane>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub link_handlers: Vec<PluginManifestLinkHandler>, + #[serde(default)] + pub source: PluginSourceInfo, + /// Warnings collected at link time or on registry load (e.g. unknown event names, + /// missing manifest file). Non-fatal — the entry is kept and surfaced by plugin.list. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub warnings: Vec<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginSourceInfo { + #[serde(default)] + pub kind: PluginSourceKind, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub owner: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub repo: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub subdir: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub requested_ref: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resolved_commit: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub managed_path: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub installed_unix_ms: Option<u64>, +} + +impl Default for PluginSourceInfo { + fn default() -> Self { + Self { + kind: PluginSourceKind::Local, + owner: None, + repo: None, + subdir: None, + requested_ref: None, + resolved_commit: None, + managed_path: None, + installed_unix_ms: None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum PluginSourceKind { + #[default] + Local, + Github, +} + +pub(crate) fn plugin_managed_path_component(value: &str) -> String { + value + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') { + ch + } else { + '_' + } + }) + .collect() +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginManifestBuild { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, + pub command: Vec<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginManifestAction { + pub id: String, + pub title: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub contexts: Vec<PluginActionContext>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, + pub command: Vec<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginManifestEventHook { + pub on: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, + pub command: Vec<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginManifestPane { + pub id: String, + pub title: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, + #[serde(default)] + pub placement: PluginPanePlacement, + pub command: Vec<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginManifestLinkHandler { + pub id: String, + pub title: String, + pub pattern: String, + pub action: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct PluginActionListParams { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plugin_id: Option<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct PluginLogListParams { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plugin_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub limit: Option<usize>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginActionInvokeParams { + pub action_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plugin_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub context: Option<PluginInvocationContext>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginCommandLogInfo { + pub log_id: String, + pub plugin_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub action_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub event: Option<String>, + pub command: Vec<String>, + pub status: PluginCommandStatus, + pub started_unix_ms: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub finished_unix_ms: Option<u64>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exit_code: Option<i32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stdout: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stderr: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option<String>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginCommandStatus { + Running, + Succeeded, + Failed, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginPlatform { + Linux, + Macos, + Windows, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginActionContext { + Global, + Workspace, + Tab, + Pane, + Selection, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginInvocationContext { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_label: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_cwd: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub worktree: Option<WorkspaceWorktreeInfo>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tab_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tab_label: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub focused_pane_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub focused_pane_cwd: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub focused_pane_agent: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub focused_pane_status: Option<AgentStatus>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub selected_text: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub invocation_source: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub correlation_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub clicked_url: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub link_handler_id: Option<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginActionInfo { + pub plugin_id: String, + pub action_id: String, + pub title: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub contexts: Vec<PluginActionContext>, + pub command: Vec<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub platforms: Option<Vec<PluginPlatform>>, +} + +impl PluginActionInfo { + pub fn qualified_id(&self) -> String { + format!("{}.{}", self.plugin_id, self.action_id) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginPaneOpenParams { + pub plugin_id: String, + pub entrypoint: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub placement: Option<PluginPanePlacement>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub target_pane_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub direction: Option<SplitDirection>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cwd: Option<String>, + #[serde(default)] + pub focus: bool, + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub env: HashMap<String, String>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum PluginPanePlacement { + #[default] + Overlay, + Split, + Tab, + Zoomed, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginPaneFocusParams { + pub pane_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginPaneCloseParams { + pub pane_id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PluginPaneInfo { + pub plugin_id: String, + pub entrypoint: String, + pub pane: PaneInfo, +} |
