Require marketplace for all plugins
Removes standalone src/version plugin config. Simplifies parsing since
every plugin must reference a marketplace.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/README.md b/README.md
index b63c181..7b495f8 100644
--- a/README.md
+++ b/README.md
@@ -64,15 +64,12 @@ plugins:
gh-pr:
marketplace: my-skills
version: v1.2.0 # override the marketplace default
-
- one-off-skill:
- src: https://github.com/other/repo # standalone plugin, no marketplace
- version: main
```
-Plugins can reference a named marketplace (inheriting `src` and `version`) or
-specify `src`/`version` directly. The plugin path within the repo is discovered
-from `.claude-plugin/marketplace.json` (or the marketplace's `manifest` override).
+Every plugin references a named marketplace (inheriting `src` and `version`).
+A plugin can override `version` to pin a different branch or tag. The plugin
+path within the repo is discovered from `.claude-plugin/marketplace.json`
+(or the marketplace's `manifest` override).
Removing a skill is done by removing it from the config and re-running
`pinch install`.
diff --git a/src/config.rs b/src/config.rs
index 4a2d891..a1df3ce 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -11,7 +11,7 @@ pub struct Plugin {
pub version: String,
/// Path to marketplace.json within the repo.
/// Defaults to `.claude-plugin/marketplace.json`.
- pub marketplace: Option<String>,
+ pub manifest: Option<String>,
}
pub fn load() -> Result<Vec<Plugin>> {
@@ -44,7 +44,6 @@ pub fn load() -> Result<Vec<Plugin>> {
let file: ConfigFile = serde_yml::from_str(&contents)
.with_context(|| format!("failed to parse {}", path.display()))?;
- // Marketplaces are scoped to this file
let marketplaces = &file.marketplaces;
for (name, entry) in file.plugins {
@@ -56,41 +55,20 @@ pub fn load() -> Result<Vec<Plugin>> {
);
}
- let (src, version, marketplace_path) = match entry.marketplace {
- Some(mkt_name) => {
- let mkt = marketplaces.get(&mkt_name).ok_or_else(|| {
- color_eyre::eyre::eyre!(
- "plugin '{}' references unknown marketplace '{}' (in {})",
- name,
- mkt_name,
- path.display()
- )
- })?;
- (
- mkt.src.clone(),
- entry.version.unwrap_or_else(|| mkt.version.clone()),
- mkt.manifest.clone(),
- )
- }
- None => {
- let src = entry.src.ok_or_else(|| {
- color_eyre::eyre::eyre!(
- "plugin '{}': must specify either 'src' or 'marketplace'",
- name
- )
- })?;
- let version = entry.version.ok_or_else(|| {
- color_eyre::eyre::eyre!("plugin '{}': 'version' is required", name)
- })?;
- (src, version, None)
- }
- };
+ let mkt = marketplaces.get(&entry.marketplace).ok_or_else(|| {
+ color_eyre::eyre::eyre!(
+ "plugin '{}' references unknown marketplace '{}' (in {})",
+ name,
+ entry.marketplace,
+ path.display()
+ )
+ })?;
plugins.push(Plugin {
name,
- src,
- version,
- marketplace: marketplace_path,
+ src: mkt.src.clone(),
+ version: entry.version.unwrap_or_else(|| mkt.version.clone()),
+ manifest: mkt.manifest.clone(),
});
}
}
@@ -107,9 +85,8 @@ struct MarketplaceEntry {
#[derive(Deserialize)]
struct PluginEntry {
- src: Option<String>,
+ marketplace: String,
version: Option<String>,
- marketplace: Option<String>,
}
#[derive(Deserialize)]
diff --git a/src/main.rs b/src/main.rs
index 7190421..e81613d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -124,7 +124,7 @@ fn do_update(names: &[String]) -> Result<()> {
// Create worktree to read marketplace.json and discover skill path
let wt_path = git::ensure_worktree(&repo_path, &rev)?;
let source =
- marketplace::find_plugin_source(&wt_path, &plugin.name, plugin.marketplace.as_deref())?;
+ marketplace::find_plugin_source(&wt_path, &plugin.name, plugin.manifest.as_deref())?;
let path = source.trim_start_matches("./").to_string();
info!(" found at {}", path);