harness: declarative claude plugin marketplaces
new `hyperhive.claudeMarketplaces` option (list of strings — URL, path, or github:owner/repo). harness boot adds each via `claude plugin marketplace add` before updating + installing the configured plugins, so specs like `foo@some-marketplace` resolve on a fresh container. idempotent: 'already exists' stderr is treated as success.
This commit is contained in:
parent
608de57924
commit
597351ca4e
2 changed files with 66 additions and 0 deletions
|
|
@ -18,6 +18,53 @@ use tokio::process::Command;
|
||||||
use crate::client;
|
use crate::client;
|
||||||
|
|
||||||
const PLUGINS_PATH: &str = "/etc/hyperhive/claude-plugins.json";
|
const PLUGINS_PATH: &str = "/etc/hyperhive/claude-plugins.json";
|
||||||
|
const MARKETPLACES_PATH: &str = "/etc/hyperhive/claude-marketplaces.json";
|
||||||
|
|
||||||
|
/// Add every marketplace from `/etc/hyperhive/claude-marketplaces.json`
|
||||||
|
/// via `claude plugin marketplace add <source>`. Idempotent: re-add of
|
||||||
|
/// an existing marketplace is treated as success (claude prints an
|
||||||
|
/// "already exists" message and exits non-zero on some versions).
|
||||||
|
/// Required before any `<plugin>@<marketplace>` install can resolve.
|
||||||
|
async fn add_marketplaces() {
|
||||||
|
let raw = match tokio::fs::read_to_string(MARKETPLACES_PATH).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let sources: Vec<String> = match serde_json::from_str(&raw) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(path = MARKETPLACES_PATH, error = ?e, "claude-marketplaces spec parse failed; skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for source in sources {
|
||||||
|
match Command::new("claude")
|
||||||
|
.args(["plugin", "marketplace", "add", &source])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(out) if out.status.success() => {
|
||||||
|
tracing::info!(source = %source, "claude plugin marketplace add ok");
|
||||||
|
}
|
||||||
|
Ok(out) => {
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
if stderr.contains("already") {
|
||||||
|
tracing::debug!(source = %source, "marketplace already added");
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
source = %source,
|
||||||
|
status = ?out.status,
|
||||||
|
stderr = %stderr,
|
||||||
|
"claude plugin marketplace add failed (non-fatal)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(source = %source, error = ?e, "claude plugin marketplace add spawn failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Update all configured plugin marketplaces. Non-fatal — logs a warning
|
/// Update all configured plugin marketplaces. Non-fatal — logs a warning
|
||||||
/// on failure but does not abort the install sequence.
|
/// on failure but does not abort the install sequence.
|
||||||
|
|
@ -64,6 +111,7 @@ pub async fn install_configured(socket: &Path, notify_recipient: Option<&str>) {
|
||||||
if specs.is_empty() {
|
if specs.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
add_marketplaces().await;
|
||||||
update_marketplaces().await;
|
update_marketplaces().await;
|
||||||
for spec in specs {
|
for spec in specs {
|
||||||
match Command::new("claude")
|
match Command::new("claude")
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,21 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.hyperhive.claudeMarketplaces = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "github:anthropics/claude-plugins-official" ];
|
||||||
|
description = ''
|
||||||
|
Claude Code plugin marketplaces to add at harness boot. Each
|
||||||
|
entry is passed to `claude plugin marketplace add <source>`
|
||||||
|
(URL, path, or `github:owner/repo`). Idempotent — re-adding an
|
||||||
|
existing marketplace is treated as success. Required before
|
||||||
|
`hyperhive.claudePlugins` entries that reference a marketplace
|
||||||
|
(e.g. `foo@some-marketplace`). Rendered to
|
||||||
|
`/etc/hyperhive/claude-marketplaces.json`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
options.hyperhive.claudePlugins = lib.mkOption {
|
options.hyperhive.claudePlugins = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
|
|
@ -122,6 +137,9 @@
|
||||||
environment.etc."hyperhive/claude-plugins.json".text =
|
environment.etc."hyperhive/claude-plugins.json".text =
|
||||||
builtins.toJSON config.hyperhive.claudePlugins;
|
builtins.toJSON config.hyperhive.claudePlugins;
|
||||||
|
|
||||||
|
environment.etc."hyperhive/claude-marketplaces.json".text =
|
||||||
|
builtins.toJSON config.hyperhive.claudeMarketplaces;
|
||||||
|
|
||||||
boot.isNspawnContainer = true;
|
boot.isNspawnContainer = true;
|
||||||
|
|
||||||
# `claude-code` is unfree. Each per-agent container's nixosConfiguration
|
# `claude-code` is unfree. Each per-agent container's nixosConfiguration
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue