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
use std::path::Path;
use color_eyre::eyre::{Result, WrapErr, bail, eyre};
use serde::Deserialize;
const DEFAULT_PATH: &str = ".claude-plugin/marketplace.json";
#[derive(Debug, Deserialize)]
pub struct Marketplace {
pub plugins: Vec<MarketplacePlugin>,
}
#[derive(Debug, Deserialize)]
pub struct MarketplacePlugin {
pub name: String,
pub source: PluginSource,
}
/// A plugin source in marketplace.json.
///
/// Can be a relative path string (e.g. `"./plugins/my-plugin"`) or an object
/// describing an external source (github, git url, npm, pip). Pinch only
/// supports relative paths since it clones the marketplace repo directly.
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum PluginSource {
/// Relative path within the marketplace repo.
Path(String),
/// External source (github, url, npm, pip) — not supported by pinch.
External(serde_json::Value),
}
/// Find the source path for a plugin by name in the marketplace.json.
pub fn find_plugin_source(
repo_path: &Path,
plugin_name: &str,
marketplace_override: Option<&str>,
) -> Result<String> {
let manifest_path = repo_path.join(marketplace_override.unwrap_or(DEFAULT_PATH));
let contents = fs_err::read_to_string(&manifest_path)?;
let marketplace: Marketplace = serde_json::from_str(&contents)
.wrap_err_with(|| format!("failed to parse {}", manifest_path.display()))?;
let plugin = marketplace
.plugins
.iter()
.find(|mp| mp.name == plugin_name)
.ok_or_else(|| {
eyre!(
"plugin '{}' not found in {}",
plugin_name,
manifest_path.display()
)
})?;
match &plugin.source {
PluginSource::Path(path) => Ok(path.clone()),
PluginSource::External(value) => {
bail!(
"plugin '{}': external source {} — pinch only supports relative path sources",
plugin_name,
value
);
}
}
}