agpm_cli/models/
mod.rs

1//! Shared data models for AGPM operations
2//!
3//! This module provides reusable data structures that are used across
4//! different CLI commands and core operations, ensuring consistency
5//! and reducing code duplication.
6
7use clap::Args;
8use serde::{Deserialize, Serialize};
9
10/// Common dependency specification used across commands
11#[derive(Debug, Clone, Args)]
12pub struct DependencySpec {
13    /// Dependency specification string
14    ///
15    /// Format: `source:path[@version]` for Git sources or `path` for local files
16    ///
17    /// Git dependency formats:
18    /// • `source:path@version` - Git source with specific version
19    /// • `source:path` - Git source (defaults to "main")
20    ///
21    /// Local dependency formats:
22    /// • `/absolute/path/file.md` - Absolute path
23    /// • `./relative/path/file.md` - Relative path
24    /// • `file:///path/to/file.md` - File URL
25    /// • `C:\Windows\path\file.md` - Windows path
26    ///
27    /// Pattern formats (using glob patterns):
28    /// • `source:agents/*.md@v1.0` - All .md files in agents/
29    /// • `source:agents/**/review*.md` - All review files recursively
30    /// • `./local/**/*.json` - All JSON files recursively
31    ///
32    /// Examples:
33    /// • `official:agents/reviewer.md@v1.0.0`
34    /// • `community:snippets/utils.md`
35    /// • `./agents/local-agent.md`
36    /// • `../shared/resources/hook.json`
37    #[arg(value_name = "SPEC")]
38    pub spec: String,
39
40    /// Custom name for the dependency
41    ///
42    /// If not provided, the name will be derived from the file path.
43    /// This allows for more descriptive or shorter names in the manifest.
44    #[arg(long)]
45    pub name: Option<String>,
46
47    /// Force overwrite if dependency exists
48    ///
49    /// By default, adding a duplicate dependency will fail.
50    /// Use this flag to replace existing dependencies.
51    #[arg(long, short = 'f')]
52    pub force: bool,
53}
54
55/// Arguments for adding an agent dependency
56#[derive(Debug, Clone, Args)]
57pub struct AgentDependency {
58    /// Common dependency specification fields
59    #[command(flatten)]
60    pub common: DependencySpec,
61}
62
63/// Arguments for adding a snippet dependency
64#[derive(Debug, Clone, Args)]
65pub struct SnippetDependency {
66    /// Common dependency specification fields
67    #[command(flatten)]
68    pub common: DependencySpec,
69}
70
71/// Arguments for adding a command dependency
72#[derive(Debug, Clone, Args)]
73pub struct CommandDependency {
74    /// Common dependency specification fields
75    #[command(flatten)]
76    pub common: DependencySpec,
77}
78
79/// Arguments for adding an MCP server dependency
80#[derive(Debug, Clone, Args)]
81pub struct McpServerDependency {
82    /// Common dependency specification fields
83    #[command(flatten)]
84    pub common: DependencySpec,
85}
86
87/// Enum representing all possible dependency types
88#[derive(Debug, Clone)]
89pub enum DependencyType {
90    /// An agent dependency
91    Agent(AgentDependency),
92    /// A snippet dependency
93    Snippet(SnippetDependency),
94    /// A command dependency
95    Command(CommandDependency),
96    /// A script dependency
97    Script(ScriptDependency),
98    /// A hook dependency
99    Hook(HookDependency),
100    /// An MCP server dependency
101    McpServer(McpServerDependency),
102}
103
104impl DependencyType {
105    /// Get the common dependency specification
106    #[must_use]
107    pub const fn common(&self) -> &DependencySpec {
108        match self {
109            Self::Agent(dep) => &dep.common,
110            Self::Snippet(dep) => &dep.common,
111            Self::Command(dep) => &dep.common,
112            Self::Script(dep) => &dep.common,
113            Self::Hook(dep) => &dep.common,
114            Self::McpServer(dep) => &dep.common,
115        }
116    }
117
118    /// Get the resource type as a string
119    #[must_use]
120    pub const fn resource_type(&self) -> &'static str {
121        match self {
122            Self::Agent(_) => "agent",
123            Self::Snippet(_) => "snippet",
124            Self::Command(_) => "command",
125            Self::Script(_) => "script",
126            Self::Hook(_) => "hook",
127            Self::McpServer(_) => "mcp-server",
128        }
129    }
130}
131
132/// Source repository specification
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SourceSpec {
135    /// Name for the source
136    pub name: String,
137
138    /// Git repository URL
139    pub url: String,
140}
141
142/// Resource installation options
143#[derive(Debug, Clone, Default)]
144pub struct InstallOptions {
145    /// Skip installation, only update lockfile
146    pub no_install: bool,
147
148    /// Force reinstallation even if up to date
149    pub force: bool,
150
151    /// Suppress progress indicators
152    pub quiet: bool,
153
154    /// Use cached data only, don't fetch updates
155    pub offline: bool,
156}
157
158/// Resource update options
159#[derive(Debug, Clone, Default)]
160pub struct UpdateOptions {
161    /// Update all dependencies
162    pub all: bool,
163
164    /// Specific dependencies to update
165    pub dependencies: Vec<String>,
166
167    /// Allow updating to incompatible versions
168    pub breaking: bool,
169
170    /// Suppress progress indicators
171    pub quiet: bool,
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_dependency_type_common() {
180        let agent = DependencyType::Agent(AgentDependency {
181            common: DependencySpec {
182                spec: "test:agent.md".to_string(),
183                name: None,
184                force: false,
185            },
186        });
187
188        assert_eq!(agent.common().spec, "test:agent.md");
189        assert_eq!(agent.resource_type(), "agent");
190    }
191
192    #[test]
193    fn test_mcp_server_dependency() {
194        let mcp = DependencyType::McpServer(McpServerDependency {
195            common: DependencySpec {
196                spec: "test:mcp.toml".to_string(),
197                name: Some("test-server".to_string()),
198                force: true,
199            },
200        });
201
202        assert_eq!(mcp.common().spec, "test:mcp.toml");
203        assert_eq!(mcp.common().name, Some("test-server".to_string()));
204        assert!(mcp.common().force);
205        assert_eq!(mcp.resource_type(), "mcp-server");
206    }
207}
208
209/// Arguments for adding a script dependency
210#[derive(Debug, Clone, Args)]
211pub struct ScriptDependency {
212    /// Common dependency specification fields
213    #[command(flatten)]
214    pub common: DependencySpec,
215}
216
217/// Arguments for adding a hook dependency
218#[derive(Debug, Clone, Args)]
219pub struct HookDependency {
220    /// Common dependency specification fields
221    #[command(flatten)]
222    pub common: DependencySpec,
223}