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
change mkvkpoyyyrunvzxxnvwrqszzvvmrptso
commit be9060383794f7217a6c1b935863ac5086f6fb9f
author Alpha Chen <alpha@kejadlen.dev>
date
parent xvtqoxyu
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);