use clap::Subcommand;
use crate::registry::catalog::RegistryCatalog;
use crate::registry::installer::RegistryInstaller;
use crate::registry::manifest::ManifestKind;
#[derive(Subcommand, Debug, Clone)]
pub enum RegistryCommand {
List {
#[arg(short, long)]
kind: Option<String>,
#[arg(short, long)]
tag: Option<String>,
#[arg(short, long)]
verbose: bool,
},
Info {
name: String,
},
Install {
name: String,
#[arg(short, long)]
force: bool,
#[arg(long)]
build: bool,
},
InstallDefaults {
#[arg(short, long)]
force: bool,
#[arg(long)]
build: bool,
},
}
pub async fn run_registry_command(cmd: RegistryCommand) -> anyhow::Result<()> {
let registry_dir = RegistryCatalog::find_dir();
let catalog = if let Some(ref dir) = registry_dir {
RegistryCatalog::load(dir)?
} else {
RegistryCatalog::load_or_embedded()?
};
let repo_root = registry_dir
.as_ref()
.and_then(|d| d.parent().map(|p| p.to_path_buf()))
.unwrap_or_default();
match cmd {
RegistryCommand::List { kind, tag, verbose } => {
cmd_list(&catalog, kind.as_deref(), tag.as_deref(), verbose)
}
RegistryCommand::Info { name } => cmd_info(&catalog, &name),
RegistryCommand::Install { name, force, build } => {
cmd_install(&catalog, &repo_root, &name, force, build).await
}
RegistryCommand::InstallDefaults { force, build } => {
cmd_install(&catalog, &repo_root, "default", force, build).await
}
}
}
fn cmd_list(
catalog: &RegistryCatalog,
kind: Option<&str>,
tag: Option<&str>,
verbose: bool,
) -> anyhow::Result<()> {
let kind_filter = match kind {
Some("tool" | "tools") => Some(ManifestKind::Tool),
Some("channel" | "channels") => Some(ManifestKind::Channel),
Some(other) => anyhow::bail!("Unknown kind '{}'. Use 'tool' or 'channel'.", other),
None => None,
};
let manifests = catalog.list(kind_filter, tag);
if manifests.is_empty() {
println!("No extensions found matching the criteria.");
return Ok(());
}
if verbose {
println!(
"{:<20} {:<8} {:<8} {:<10} DESCRIPTION",
"NAME", "KIND", "VERSION", "AUTH"
);
println!("{}", "-".repeat(80));
} else {
println!("{:<20} {:<8} DESCRIPTION", "NAME", "KIND");
println!("{}", "-".repeat(60));
}
for m in &manifests {
if verbose {
let auth = m
.auth_summary
.as_ref()
.and_then(|a| a.method.as_deref())
.unwrap_or("none");
println!(
"{:<20} {:<8} {:<8} {:<10} {}",
m.name,
m.kind,
m.version.as_deref().unwrap_or("-"),
auth,
m.description
);
} else {
println!("{:<20} {:<8} {}", m.name, m.kind, m.description);
}
}
println!("\n{} extension(s) found.", manifests.len());
let bundle_names = catalog.bundle_names();
if !bundle_names.is_empty() {
println!("\nBundles available: {}", bundle_names.join(", "));
println!("Use `ironclaw registry info <bundle>` for details.");
}
Ok(())
}
fn cmd_info(catalog: &RegistryCatalog, name: &str) -> anyhow::Result<()> {
if let Some(bundle) = catalog.get_bundle(name) {
println!("Bundle: {}", bundle.display_name);
if let Some(desc) = &bundle.description {
println!(" {}", desc);
}
println!("\nExtensions:");
for ext_key in &bundle.extensions {
if let Some(m) = catalog.get(ext_key) {
println!(" {} - {} ({})", ext_key, m.description, m.kind);
} else {
println!(" {} (not found in registry)", ext_key);
}
}
if let Some(shared) = &bundle.shared_auth {
println!("\nShared auth: {}", shared);
}
return Ok(());
}
let manifest = catalog
.get_strict(name)
.map_err(|e| anyhow::anyhow!("{}", e))?;
println!("{} ({})", manifest.display_name, manifest.kind);
if let Some(ref version) = manifest.version {
println!(" Version: {}", version);
}
println!(" {}", manifest.description);
if !manifest.keywords.is_empty() {
println!(" Keywords: {}", manifest.keywords.join(", "));
}
if let Some(ref source) = manifest.source {
println!("\nSource:");
println!(" Directory: {}", source.dir);
println!(" Crate: {}", source.crate_name);
println!(" Capabilities: {}", source.capabilities);
}
if let Some(ref url) = manifest.url {
println!("\nMCP Server URL: {}", url);
}
if let Some(artifact) = manifest.artifacts.get("wasm32-wasip2") {
println!("\nArtifact (wasm32-wasip2):");
match &artifact.url {
Some(url) => println!(" URL: {}", url),
None => println!(" URL: (not yet published)"),
}
match &artifact.sha256 {
Some(sha) => println!(" SHA256: {}", sha),
None => println!(" SHA256: (not yet computed)"),
}
}
if let Some(auth) = &manifest.auth_summary {
println!("\nAuthentication:");
if let Some(method) = &auth.method {
println!(" Method: {}", method);
}
if let Some(provider) = &auth.provider {
println!(" Provider: {}", provider);
}
if !auth.secrets.is_empty() {
println!(" Secrets: {}", auth.secrets.join(", "));
}
if let Some(shared) = &auth.shared_auth {
println!(" Shared with: {}", shared);
}
if let Some(url) = &auth.setup_url {
println!(" Setup: {}", url);
}
}
if !manifest.tags.is_empty() {
println!("\nTags: {}", manifest.tags.join(", "));
}
Ok(())
}
async fn cmd_install(
catalog: &RegistryCatalog,
repo_root: &std::path::Path,
name: &str,
force: bool,
prefer_build: bool,
) -> anyhow::Result<()> {
let installer = RegistryInstaller::with_defaults(repo_root.to_path_buf());
let (manifests, bundle) = catalog.resolve(name)?;
if manifests.is_empty() {
anyhow::bail!("No extensions found for '{}'.", name);
}
if let Some(bundle_def) = bundle {
println!(
"Installing bundle '{}' ({} extensions)...\n",
bundle_def.display_name,
manifests.len()
);
let (outcomes, hints) = installer
.install_bundle(&manifests, bundle_def, force, prefer_build)
.await;
println!("\n--- Results ---");
for outcome in &outcomes {
let caps_status = if outcome.has_capabilities { "+" } else { "-" };
println!(
" [{}] {} ({}) -> {}",
caps_status,
outcome.name,
outcome.kind,
outcome.wasm_path.display()
);
for w in &outcome.warnings {
println!(" Warning: {}", w);
}
}
if !hints.is_empty() {
println!("\nAuth setup:");
for hint in &hints {
println!("{}", hint);
}
}
println!(
"\nInstalled {}/{} extensions.",
outcomes.len(),
manifests.len()
);
} else {
let manifest = manifests[0];
let outcome = installer.install(manifest, force, prefer_build).await?;
println!("\nInstalled successfully:");
println!(" Name: {}", outcome.name);
println!(" Kind: {}", outcome.kind);
println!(" WASM: {}", outcome.wasm_path.display());
println!(" Capabilities: {}", outcome.has_capabilities);
if let Some(auth) = &manifest.auth_summary
&& auth.method.as_deref() != Some("none")
{
println!(
"\nNext step: authenticate with `ironclaw tool auth {}`",
manifest.name
);
if let Some(url) = &auth.setup_url {
println!(" Setup credentials at: {}", url);
}
}
}
Ok(())
}