mod entries;
mod external;
mod managed_mcp;
pub mod skill_frontmatter;
use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub use crate::bridge::ids::ManifestSignature;
use crate::bridge::manifest_version::ManifestVersion;
use crate::bridge::update_policy::AutoUpdatePolicy;
use systemprompt_identifiers::{ApiKeyId, MarketplaceId, PluginId, TenantId, UserId};
pub use entries::{
AgentEntry, ArtifactEntry, HookEntry, PluginEntry, PluginFile, RuleEntry, SkillEntry,
SkillPublication,
};
pub use external::{
ExternalPluginSkills, ManifestExternalMarketplace, ManifestExternalMarketplaceSource,
ManifestExternalPlugin, ManifestExternalPluginSource,
};
pub use managed_mcp::ManagedMcpServer;
pub const MANIFEST_SCHEMA_VERSION: u32 = 1;
#[must_use]
pub const fn min_bridge_version() -> semver::Version {
semver::Version::new(0, 28, 0)
}
pub const EXTERNAL_MARKETPLACE_REF_MIN_BRIDGE: semver::Version = semver::Version::new(0, 63, 0);
#[must_use]
pub fn manifest_min_bridge_version(marketplaces: &[ManifestMarketplace]) -> semver::Version {
let floor = min_bridge_version();
let carries_ref = marketplaces
.iter()
.flat_map(|marketplace| &marketplace.external_marketplaces)
.any(|external| external.source.reference.is_some());
if carries_ref {
floor.max(EXTERNAL_MARKETPLACE_REF_MIN_BRIDGE)
} else {
floor
}
}
#[must_use]
pub fn bridge_version_is_supported(reported: &str, floor: &semver::Version) -> bool {
semver::Version::parse(reported).is_ok_and(|reported| reported >= *floor)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedManifestEnvelope {
pub payload: String,
pub signature: ManifestSignature,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedManifest {
#[serde(default)]
pub min_schema_version: u32,
#[serde(default)]
pub min_bridge_version: Option<semver::Version>,
pub manifest_version: ManifestVersion,
pub issued_at: DateTime<Utc>,
pub not_before: DateTime<Utc>,
pub user_id: UserId,
pub tenant_id: Option<TenantId>,
#[serde(default)]
pub user: Option<UserInfo>,
pub plugins: Vec<PluginEntry>,
#[serde(default)]
pub skills: Vec<SkillEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rules: Vec<RuleEntry>,
#[serde(default)]
pub agents: Vec<AgentEntry>,
#[serde(default)]
pub hooks: Vec<HookEntry>,
pub managed_mcp_servers: Vec<ManagedMcpServer>,
pub revocations: Vec<ApiKeyId>,
#[serde(default)]
pub enabled_hosts: Vec<String>,
#[serde(default)]
pub host_model_protocols: BTreeMap<String, Vec<String>>,
#[serde(default)]
pub artifacts: Vec<ArtifactEntry>,
#[serde(default)]
pub allow_claude_ai_connectors: bool,
#[serde(
default,
skip_serializing_if = "crate::bridge::desktop_policy::DesktopPolicy::is_empty"
)]
pub desktop_policy: crate::bridge::desktop_policy::DesktopPolicy,
#[serde(default)]
pub auto_update: AutoUpdatePolicy,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub diagnostics: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub marketplaces: Vec<ManifestMarketplace>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestMarketplace {
pub id: MarketplaceId,
pub name: String,
pub plugin_ids: Vec<PluginId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow_cross_marketplace_dependencies_on: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub external_marketplaces: Vec<ManifestExternalMarketplace>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub external_plugins: Vec<ManifestExternalPlugin>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claude_code: Option<ManifestClaudeCode>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestClaudeCode {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_listing_budget_chars: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub id: UserId,
pub name: String,
pub email: String,
#[serde(default)]
pub display_name: Option<String>,
#[serde(default)]
pub roles: Vec<String>,
}