1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Mutation operations for manifest data.
//!
//! This module contains all methods that modify the manifest, including:
//! - Adding sources
//! - Adding dependencies
//! - Getting mutable references to dependencies
use crate::manifest::{Manifest, ResourceDependency};
use std::collections::HashMap;
impl Manifest {
/// Get mutable dependencies for a specific resource type.
///
/// Returns a mutable reference to the `HashMap` of dependencies for the specified resource type.
#[must_use]
pub fn get_dependencies_mut(
&mut self,
resource_type: crate::core::ResourceType,
) -> Option<&mut HashMap<String, ResourceDependency>> {
use crate::core::ResourceType;
match resource_type {
ResourceType::Agent => Some(&mut self.agents),
ResourceType::Snippet => Some(&mut self.snippets),
ResourceType::Command => Some(&mut self.commands),
ResourceType::Script => Some(&mut self.scripts),
ResourceType::Hook => Some(&mut self.hooks),
ResourceType::McpServer => Some(&mut self.mcp_servers),
}
}
/// Add or update a source repository in the `[sources]` section.
///
/// Sources map convenient names to Git repository URLs. These names can
/// then be referenced in dependency specifications to avoid repeating
/// long URLs throughout the manifest.
///
/// # Parameters
///
/// - `name`: Short, convenient name for the source (e.g., "official", "community")
/// - `url`: Git repository URL (HTTPS, SSH, or file:// protocol)
///
/// # URL Validation
///
/// The URL is not validated when added - validation occurs during
/// [`Self::validate`]. Supported URL formats:
/// - `https://github.com/owner/repo.git`
/// - `git@github.com:owner/repo.git`
/// - `file:///absolute/path/to/repo`
/// - `file:///path/to/local/repo`
///
/// # Examples
///
/// ```rust,no_run
/// use agpm_cli::manifest::Manifest;
///
/// let mut manifest = Manifest::new();
///
/// // Add public repository
/// manifest.add_source(
/// "community".to_string(),
/// "https://github.com/claude-community/resources.git".to_string()
/// );
///
/// // Add private repository (SSH)
/// manifest.add_source(
/// "private".to_string(),
/// "git@github.com:company/private-resources.git".to_string()
/// );
///
/// // Add local repository
/// manifest.add_source(
/// "local".to_string(),
/// "file:///home/user/my-resources".to_string()
/// );
/// ```
///
/// # Security Note
///
/// Never include authentication tokens in the URL. Use SSH keys or
/// configure authentication globally in `~/.agpm/config.toml`.
pub fn add_source(&mut self, name: String, url: String) {
self.sources.insert(name, url);
}
/// Add or update a dependency in the appropriate section.
///
/// Adds the dependency to either the `[agents]` or `[snippets]` section
/// based on the `is_agent` parameter. If a dependency with the same name
/// already exists in the target section, it will be replaced.
///
/// For commands and other resource types, use [`Self::add_typed_dependency`]
/// which provides explicit control over resource types.
///
/// # Parameters
///
/// - `name`: Unique name for the dependency within its section
/// - `dep`: The dependency specification (Simple or Detailed)
/// - `is_agent`: If true, adds to `[agents]`; if false, adds to `[snippets]`
///
/// # Validation
///
/// The dependency is not validated when added - validation occurs during
/// [`Self::validate`]. This allows for building manifests incrementally
/// before all sources are defined.
///
/// # Examples
///
/// ```rust,no_run
/// use agpm_cli::manifest::{Manifest, ResourceDependency, DetailedDependency};
///
/// let mut manifest = Manifest::new();
///
/// // Add local agent dependency
/// manifest.add_dependency(
/// "helper".to_string(),
/// ResourceDependency::Simple("../local/helper.md".to_string()),
/// true // is_agent = true
/// );
///
/// // Add remote snippet dependency
/// manifest.add_dependency(
/// "utils".to_string(),
/// ResourceDependency::Detailed(Box::new(DetailedDependency {
/// source: Some("community".to_string()),
/// path: "snippets/utils.md".to_string(),
/// version: Some("v1.0.0".to_string()),
/// branch: None,
/// rev: None,
/// command: None,
/// args: None,
/// target: None,
/// filename: None,
/// dependencies: None,
/// tool: Some("claude-code".to_string()),
/// flatten: None,
/// install: None,
/// template_vars: Some(serde_json::Value::Object(serde_json::Map::new())),
/// })),
/// false // is_agent = false (snippet)
/// );
/// ```
///
/// # Name Conflicts
///
/// This method allows the same dependency name to exist in both the
/// `[agents]` and `[snippets]` sections. However, some operations like
/// [`Self::get_dependency`] will prefer agents over snippets when
/// searching by name.
pub fn add_dependency(&mut self, name: String, dep: ResourceDependency, is_agent: bool) {
if is_agent {
self.agents.insert(name, dep);
} else {
self.snippets.insert(name, dep);
}
}
/// Add or update a dependency with specific resource type.
///
/// This is the preferred method for adding dependencies as it explicitly
/// specifies the resource type using the `ResourceType` enum.
///
/// # Examples
///
/// ```rust,no_run
/// use agpm_cli::manifest::{Manifest, ResourceDependency};
/// use agpm_cli::core::ResourceType;
///
/// let mut manifest = Manifest::new();
///
/// // Add command dependency
/// manifest.add_typed_dependency(
/// "build".to_string(),
/// ResourceDependency::Simple("../commands/build.md".to_string()),
/// ResourceType::Command
/// );
/// ```
pub fn add_typed_dependency(
&mut self,
name: String,
dep: ResourceDependency,
resource_type: crate::core::ResourceType,
) {
match resource_type {
crate::core::ResourceType::Agent => {
self.agents.insert(name, dep);
}
crate::core::ResourceType::Snippet => {
self.snippets.insert(name, dep);
}
crate::core::ResourceType::Command => {
self.commands.insert(name, dep);
}
crate::core::ResourceType::McpServer => {
// MCP servers don't use ResourceDependency, they have their own type
// This method shouldn't be called for MCP servers
panic!("Use add_mcp_server() for MCP server dependencies");
}
crate::core::ResourceType::Script => {
self.scripts.insert(name, dep);
}
crate::core::ResourceType::Hook => {
self.hooks.insert(name, dep);
}
}
}
/// Add or update an MCP server configuration.
///
/// MCP servers now use standard `ResourceDependency` format,
/// pointing to JSON configuration files in source repositories.
///
/// # Examples
///
/// ```rust,no_run,ignore
/// use agpm_cli::manifest::{Manifest, ResourceDependency};
///
/// let mut manifest = Manifest::new();
///
/// // Add MCP server from source repository
/// manifest.add_mcp_server(
/// "filesystem".to_string(),
/// ResourceDependency::Simple("../local/mcp-servers/filesystem.json".to_string())
/// );
/// ```
pub fn add_mcp_server(&mut self, name: String, dependency: ResourceDependency) {
self.mcp_servers.insert(name, dependency);
}
}