Install an integration package from a local directory (a synced cache clone or a `--from` checkout).
(pkg_dir: &Path, opts: &InstallOptions)
| 102 | /// Install an integration package from a local directory (a synced cache |
| 103 | /// clone or a `--from` checkout). |
| 104 | pub fn install_from_dir(pkg_dir: &Path, opts: &InstallOptions) -> AgentResult<InstallOutcome> { |
| 105 | let manifest = IntegrationManifest::load(pkg_dir)?; |
| 106 | |
| 107 | if let Some(expect) = &opts.expect_agent { |
| 108 | if &manifest.agent != expect { |
| 109 | return Err(AgentError::Integration { |
| 110 | agent: expect.clone(), |
| 111 | reason: format!( |
| 112 | "package at {} is for agent '{}'", |
| 113 | pkg_dir.display(), |
| 114 | manifest.agent |
| 115 | ), |
| 116 | }); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | manifest.check_cli_version(&opts.cli_version)?; |
| 121 | |
| 122 | let previous = Receipt::load(&manifest.agent)?; |
| 123 | |
| 124 | let mut installed = Vec::new(); |
| 125 | let mut refreshed = Vec::new(); |
| 126 | let mut skipped = Vec::new(); |
| 127 | let mut receipt_files = Vec::new(); |
| 128 | |
| 129 | for entry in &manifest.files { |
| 130 | reject_parent_traversal(&manifest.agent, &entry.src)?; |
| 131 | let src = pkg_dir.join(&entry.src); |
| 132 | if !src.is_file() { |
| 133 | return Err(AgentError::Integration { |
| 134 | agent: manifest.agent.clone(), |
| 135 | reason: format!("manifest file source missing: {}", src.display()), |
| 136 | }); |
| 137 | } |
| 138 | let dst = expand_dst(&entry.dst)?; |
| 139 | copy_one( |
| 140 | &manifest.agent, |
| 141 | &src, |
| 142 | &dst, |
| 143 | previous.as_ref(), |
| 144 | opts.force, |
| 145 | &mut installed, |
| 146 | &mut refreshed, |
| 147 | &mut skipped, |
| 148 | &mut receipt_files, |
| 149 | )?; |
| 150 | } |
| 151 | |
| 152 | // Auto-install skills from the shared cache via [skills] block. |
| 153 | // The skill list comes from the atomic-skills package's own manifest |
| 154 | // ([[declared-skill]] entries) — not from globbing the filesystem. |
| 155 | // Adding a skill to atomic-skills = one [[declared-skill]] entry in its |
| 156 | // manifest. Zero plugin manifest changes. |
| 157 | if let Some(ref config) = manifest.skills_config { |
| 158 | let cache = opts |
| 159 | .skills_cache_dir |
| 160 | .as_deref() |
| 161 | .ok_or_else(|| AgentError::Integration { |
no test coverage detected