refactor: Nest plugins under marketplaces in config
Removes the indirection of a separate top-level plugins map where each
entry back-references a marketplace by name.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/README.md b/README.md
index 3778a7f..89967da 100644
--- a/README.md
+++ b/README.md
@@ -60,22 +60,19 @@ appear in different files at different versions.
marketplaces:
my-skills:
src: https://github.com/user/skills-repo
- version: main # default version (branch/tag) for plugins from this marketplace
+ version: main # branch/tag to track
manifest: custom/path/marketplace.json # optional, defaults to .claude-plugin/marketplace.json
-
-plugins:
- jj:
- marketplace: my-skills
-
- gh-pr:
- marketplace: my-skills
+ plugins:
+ - jj
+ - gh-pr
```
-Every plugin references a named marketplace (inheriting `src`, `version`, and
-`manifest`). The plugin path within the repo is discovered from
-`.claude-plugin/marketplace.json` (or the marketplace's `manifest` override).
+Plugins are listed under their marketplace. Each plugin inherits the
+marketplace's `src`, `version`, and `manifest`. 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
+Removing a plugin is done by removing it from the config and re-running
`pinch install`.
## Lockfile
diff --git a/src/config.rs b/src/config.rs
index fbe009b..605b45a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use std::collections::HashSet;
-use color_eyre::eyre::{Result, WrapErr, bail, eyre};
+use color_eyre::eyre::{Result, WrapErr, bail};
use serde::Deserialize;
#[derive(Debug, Clone)]
@@ -44,32 +44,23 @@ pub fn load() -> Result<Vec<Plugin>> {
let file: ConfigFile = serde_yml::from_str(&contents)
.wrap_err_with(|| format!("failed to parse {}", path.display()))?;
- let marketplaces = &file.marketplaces;
-
- for (name, entry) in file.plugins {
- if !seen_names.insert(name.clone()) {
- bail!(
- "duplicate plugin name '{}' (found in {})",
+ for (_mkt_name, mkt) in file.marketplaces {
+ for name in mkt.plugins {
+ if !seen_names.insert(name.clone()) {
+ bail!(
+ "duplicate plugin name '{}' (found in {})",
+ name,
+ path.display()
+ );
+ }
+
+ plugins.push(Plugin {
name,
- path.display()
- );
+ src: mkt.src.clone(),
+ version: mkt.version.clone(),
+ manifest: mkt.manifest.clone(),
+ });
}
-
- let mkt = marketplaces.get(&entry.marketplace).ok_or_else(|| {
- eyre!(
- "plugin '{}' references unknown marketplace '{}' (in {})",
- name,
- entry.marketplace,
- path.display()
- )
- })?;
-
- plugins.push(Plugin {
- name,
- src: mkt.src.clone(),
- version: mkt.version.clone(),
- manifest: mkt.manifest.clone(),
- });
}
}
@@ -81,17 +72,12 @@ struct MarketplaceEntry {
src: String,
version: String,
manifest: Option<String>,
-}
-
-#[derive(Deserialize)]
-struct PluginEntry {
- marketplace: String,
+ #[serde(default)]
+ plugins: Vec<String>,
}
#[derive(Deserialize)]
struct ConfigFile {
#[serde(default)]
marketplaces: BTreeMap<String, MarketplaceEntry>,
- #[serde(default)]
- plugins: BTreeMap<String, PluginEntry>,
}