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 DEPENDENCIES (from a repository source defined in `[sources]`):
18    ///   source:path@version       - Specific version (tag/branch/commit)
19    ///   source:path               - Defaults to "main" branch
20    ///
21    /// Examples:
22    ///   official:agents/code-reviewer.md@v1.0.0    - Specific version tag
23    ///   community:snippets/python-utils.md@main    - Branch name
24    ///   myrepo:commands/deploy.md@abc123f          - Commit SHA
25    ///   community:hooks/pre-commit.json            - Defaults to "main"
26    ///
27    /// LOCAL FILE DEPENDENCIES:
28    ///   ./path/file.md            - Relative to current directory
29    ///   ../path/file.md           - Parent directory
30    ///   /absolute/path/file.md    - Absolute path (Unix/macOS)
31    ///   C:\path\file.md           - Absolute path (Windows)
32    ///
33    /// Examples:
34    ///   ./agents/my-agent.md                       - Project agent
35    ///   ../shared-resources/common-snippet.md      - Shared resource
36    ///   /usr/local/share/agpm/hooks/lint.json      - System-wide hook
37    ///
38    /// PATTERN DEPENDENCIES (glob patterns for multiple files):
39    ///   source:dir/*.md@version   - All .md files in directory
40    ///   source:dir/**/*.md        - All .md files recursively
41    ///
42    /// Examples:
43    ///   community:agents/ai/*.md@v2.0.0            - All AI agents
44    ///   official:agents/**/review*.md@v1.5.0       - All review agents (recursive)
45    ///   ./local-agents/*.md                        - All local agents
46    ///
47    /// Notes:
48    /// - Version is optional for Git sources (defaults to "main")
49    /// - Version is not applicable for local file paths
50    /// - Use --name to specify a custom dependency name
51    /// - Patterns require --name to provide a meaningful dependency name
52    #[arg(
53        value_name = "SPEC",
54        help = "Dependency spec: 'source:path@version' for Git (e.g., community:agents/helper.md@v1.0.0) or './path' for local files. Use --help for more examples"
55    )]
56    pub spec: String,
57
58    /// Custom name for the dependency
59    ///
60    /// If not provided, the name will be derived from the file path.
61    /// This allows for more descriptive or shorter names in the manifest.
62    #[arg(long)]
63    pub name: Option<String>,
64
65    /// Target tool for the dependency
66    ///
67    /// Specifies which AI coding tool this resource is for.
68    /// Supported values: claude-code, opencode, agpm
69    ///
70    /// Examples:
71    ///   --tool claude-code  - Install to .claude/ (default for agents, commands, scripts, hooks)
72    ///   --tool opencode     - Install to .opencode/
73    ///   --tool agpm         - Install to .agpm/ (default for snippets)
74    #[arg(long)]
75    pub tool: Option<String>,
76
77    /// Custom installation target path (relative to resource directory)
78    ///
79    /// Override the default installation path. The path is relative to the
80    /// resource type's default directory (e.g., .claude/agents/).
81    ///
82    /// IMPORTANT: Since v0.3.18+, custom targets are relative to the resource
83    /// directory, not the project root.
84    ///
85    /// Examples:
86    ///   --target custom/special.md       - Install to .claude/agents/custom/special.md
87    ///   --target experimental/test.md    - Install to .claude/commands/experimental/test.md
88    #[arg(long)]
89    pub target: Option<String>,
90
91    /// Custom filename for the installed resource
92    ///
93    /// Override the default filename derived from the source path.
94    /// Use this to rename resources during installation.
95    ///
96    /// Examples:
97    ///   --filename my-reviewer.md    - Install as my-reviewer.md instead of original name
98    ///   --filename helper.json       - Rename JSON file during installation
99    #[arg(long)]
100    pub filename: Option<String>,
101
102    /// Force overwrite if dependency exists
103    ///
104    /// By default, adding a duplicate dependency will fail.
105    /// Use this flag to replace existing dependencies.
106    #[arg(long, short = 'f')]
107    pub force: bool,
108
109    /// Skip automatic installation after adding dependency
110    ///
111    /// By default, the dependency is automatically installed after being added
112    /// to the manifest. Use this flag to only update the manifest without
113    /// installing the dependency files.
114    ///
115    /// Examples:
116    ///   --no-install    - Add to manifest only, skip installation
117    #[arg(long)]
118    pub no_install: bool,
119}
120
121/// Arguments for adding an agent dependency
122#[derive(Debug, Clone, Args)]
123pub struct AgentDependency {
124    /// Common dependency specification fields
125    #[command(flatten)]
126    pub common: DependencySpec,
127}
128
129/// Arguments for adding a snippet dependency
130#[derive(Debug, Clone, Args)]
131pub struct SnippetDependency {
132    /// Common dependency specification fields
133    #[command(flatten)]
134    pub common: DependencySpec,
135}
136
137/// Arguments for adding a command dependency
138#[derive(Debug, Clone, Args)]
139pub struct CommandDependency {
140    /// Common dependency specification fields
141    #[command(flatten)]
142    pub common: DependencySpec,
143}
144
145/// Arguments for adding an MCP server dependency
146#[derive(Debug, Clone, Args)]
147pub struct McpServerDependency {
148    /// Common dependency specification fields
149    #[command(flatten)]
150    pub common: DependencySpec,
151}
152
153/// Enum representing all possible dependency types
154#[derive(Debug, Clone)]
155pub enum DependencyType {
156    /// An agent dependency
157    Agent(AgentDependency),
158    /// A snippet dependency
159    Snippet(SnippetDependency),
160    /// A command dependency
161    Command(CommandDependency),
162    /// A script dependency
163    Script(ScriptDependency),
164    /// A hook dependency
165    Hook(HookDependency),
166    /// An MCP server dependency
167    McpServer(McpServerDependency),
168}
169
170impl DependencyType {
171    /// Get the common dependency specification
172    #[must_use]
173    pub const fn common(&self) -> &DependencySpec {
174        match self {
175            Self::Agent(dep) => &dep.common,
176            Self::Snippet(dep) => &dep.common,
177            Self::Command(dep) => &dep.common,
178            Self::Script(dep) => &dep.common,
179            Self::Hook(dep) => &dep.common,
180            Self::McpServer(dep) => &dep.common,
181        }
182    }
183
184    /// Get the resource type as a string
185    #[must_use]
186    pub const fn resource_type(&self) -> &'static str {
187        match self {
188            Self::Agent(_) => "agent",
189            Self::Snippet(_) => "snippet",
190            Self::Command(_) => "command",
191            Self::Script(_) => "script",
192            Self::Hook(_) => "hook",
193            Self::McpServer(_) => "mcp-server",
194        }
195    }
196}
197
198/// Source repository specification
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct SourceSpec {
201    /// Name for the source
202    pub name: String,
203
204    /// Git repository URL
205    pub url: String,
206}
207
208/// Resource installation options
209#[derive(Debug, Clone, Default)]
210pub struct InstallOptions {
211    /// Skip installation, only update lockfile
212    pub no_install: bool,
213
214    /// Force reinstallation even if up to date
215    pub force: bool,
216
217    /// Suppress progress indicators
218    pub quiet: bool,
219
220    /// Use cached data only, don't fetch updates
221    pub offline: bool,
222}
223
224/// Resource update options
225#[derive(Debug, Clone, Default)]
226pub struct UpdateOptions {
227    /// Update all dependencies
228    pub all: bool,
229
230    /// Specific dependencies to update
231    pub dependencies: Vec<String>,
232
233    /// Allow updating to incompatible versions
234    pub breaking: bool,
235
236    /// Suppress progress indicators
237    pub quiet: bool,
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_dependency_type_common() {
246        let agent = DependencyType::Agent(AgentDependency {
247            common: DependencySpec {
248                spec: "test:agent.md".to_string(),
249                name: None,
250                tool: None,
251                target: None,
252                filename: None,
253                force: false,
254                no_install: false,
255            },
256        });
257
258        assert_eq!(agent.common().spec, "test:agent.md");
259        assert_eq!(agent.resource_type(), "agent");
260    }
261
262    #[test]
263    fn test_mcp_server_dependency() {
264        let mcp = DependencyType::McpServer(McpServerDependency {
265            common: DependencySpec {
266                spec: "test:mcp.toml".to_string(),
267                name: Some("test-server".to_string()),
268                tool: None,
269                target: None,
270                filename: None,
271                force: true,
272                no_install: false,
273            },
274        });
275
276        assert_eq!(mcp.common().spec, "test:mcp.toml");
277        assert_eq!(mcp.common().name, Some("test-server".to_string()));
278        assert!(mcp.common().force);
279        assert_eq!(mcp.resource_type(), "mcp-server");
280    }
281}
282
283/// Arguments for adding a script dependency
284#[derive(Debug, Clone, Args)]
285pub struct ScriptDependency {
286    /// Common dependency specification fields
287    #[command(flatten)]
288    pub common: DependencySpec,
289}
290
291/// Arguments for adding a hook dependency
292#[derive(Debug, Clone, Args)]
293pub struct HookDependency {
294    /// Common dependency specification fields
295    #[command(flatten)]
296    pub common: DependencySpec,
297}