use runx_runtime::registry::{InstallStatus, RegistrySkillResolution, TrustTier};
use super::remote_publish::HostedSkillPublishResult;
use super::{RegistryCliError, RegistryCliOutput, internal_error};
#[derive(serde::Serialize)]
pub(super) struct RegistryEnvelope<T> {
pub(super) status: &'static str,
pub(super) registry: T,
}
#[derive(serde::Serialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub(super) enum RegistryPayload {
Search {
source: &'static str,
query: String,
results: Vec<runx_runtime::registry::RegistrySearchResult>,
},
Read {
source: &'static str,
r#ref: String,
skill: Box<runx_runtime::registry::RegistrySkillDetail>,
},
Resolve {
source: &'static str,
r#ref: String,
resolution: RemoteOrLocalResolution,
},
Install {
source: &'static str,
r#ref: String,
install: Box<runx_runtime::registry::InstallLocalSkillResult>,
receipt_metadata: runx_contracts::JsonObject,
},
Publish {
publish: PublishPayload,
},
}
#[derive(serde::Serialize)]
#[serde(tag = "target", rename_all = "snake_case")]
pub(super) enum PublishPayload {
Hosted(Box<HostedSkillPublishResult>),
Local(Box<runx_runtime::registry::PublishSkillMarkdownResult>),
}
#[derive(serde::Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub(super) enum RemoteOrLocalResolution {
Remote(Box<runx_runtime::registry::RegistrySkillDetail>),
Local(Box<RegistrySkillResolution>),
}
pub(super) fn write_output<T: serde::Serialize>(
json: bool,
value: &T,
human: impl FnOnce() -> String,
) -> Result<RegistryCliOutput, RegistryCliError> {
let stdout = if json {
serde_json::to_string_pretty(value)
.map(|json| format!("{json}\n"))
.map_err(|error| internal_error(error.to_string()))?
} else {
human()
};
Ok(RegistryCliOutput {
stdout,
exit_code: 0,
})
}
pub(super) fn render_search(
query: &str,
source: &str,
results: &[runx_runtime::registry::RegistrySearchResult],
) -> String {
let mut output = format!(
"\n registry search {query}\n source {source}\n results {}\n\n",
results.len()
);
for result in results {
let category = result
.category
.as_ref()
.map(|category| format!(" category {category}\n"))
.or_else(|| {
result
.source_category
.as_ref()
.map(|category| format!(" source-category {category}\n"))
})
.unwrap_or_default();
output.push_str(&format!(
" - {}@{}\n{} digest {}\n trust {}\n install {}\n run {}\n",
result.skill_id,
result.version.as_deref().unwrap_or("unknown"),
category,
result
.digest
.as_deref()
.map_or("unknown".to_owned(), digest_label),
trust_tier_label(&result.trust_tier),
result.install_command,
result.run_command,
));
}
output.push('\n');
output
}
pub(super) fn render_read(
source: &str,
registry_ref: &str,
skill: &runx_runtime::registry::RegistrySkillDetail,
) -> String {
let category = skill
.category
.as_ref()
.map(|category| format!(" category {category}\n"))
.or_else(|| {
skill
.source_category
.as_ref()
.map(|category| format!(" source category {category}\n"))
})
.unwrap_or_default();
format!(
"\n registry read {registry_ref}\n source {source}\n skill {}\n{} version {}\n digest {}\n trust {}\n signed {}\n next {}\n\n",
skill.skill_id,
category,
skill.version,
digest_label(&skill.digest),
trust_tier_label(&skill.trust_tier),
signed_manifest_label(skill.signed_manifest.as_ref()),
skill.run_command,
)
}
pub(super) fn render_resolve(
source: &str,
registry_ref: &str,
resolution: &RemoteOrLocalResolution,
) -> String {
match resolution {
RemoteOrLocalResolution::Remote(resolved) => format!(
"\n registry resolve {registry_ref}\n source {source}\n skill {}\n version {}\n digest {}\n trust {}\n signed {}\n next {}\n\n",
resolved.skill_id,
resolved.version,
digest_label(&resolved.digest),
trust_tier_label(&resolved.trust_tier),
signed_manifest_label(resolved.signed_manifest.as_ref()),
resolved.run_command,
),
RemoteOrLocalResolution::Local(resolved) => format!(
"\n registry resolve {registry_ref}\n source {source}\n skill {}\n version {}\n digest {}\n trust {}\n signed {}\n next {}\n\n",
resolved.skill_id,
resolved.version,
digest_label(&resolved.digest),
trust_tier_label(&resolved.trust_tier),
signed_manifest_label(resolved.signed_manifest.as_ref()),
resolved.run_command,
),
}
}
pub(super) fn render_install(
source: &str,
registry_ref: &str,
install: &runx_runtime::registry::InstallLocalSkillResult,
signed_manifest: Option<&runx_runtime::registry::RegistrySignedManifest>,
) -> String {
format!(
"\n registry install {registry_ref}\n source {source}\n status {}\n skill {}\n version {}\n digest {}\n trust {}\n signed {}\n destination {}\n next {}\n\n",
install_status_label(&install.status),
install.skill_id.as_deref().unwrap_or(&install.skill_name),
install.version.as_deref().unwrap_or("unknown"),
digest_label(&install.digest),
install
.trust_tier
.as_ref()
.map_or("unknown", trust_tier_label),
signed_manifest_label(signed_manifest),
install.destination.display(),
install_run_command(install),
)
}
fn install_run_command(install: &runx_runtime::registry::InstallLocalSkillResult) -> String {
match (&install.skill_id, &install.version) {
(Some(skill_id), Some(version)) => format!("runx skill {skill_id}@{version}"),
_ => format!("runx skill {}", install.skill_name),
}
}
fn signed_manifest_label(
manifest: Option<&runx_runtime::registry::RegistrySignedManifest>,
) -> String {
manifest.map_or_else(
|| "no".to_owned(),
|manifest| format!("yes ({})", manifest.signer.key_id),
)
}
fn digest_label(digest: &str) -> String {
if digest.starts_with("sha256:") {
digest.to_owned()
} else {
format!("sha256:{digest}")
}
}
fn trust_tier_label(tier: &TrustTier) -> &'static str {
match tier {
TrustTier::FirstParty => "first_party",
TrustTier::Verified => "verified",
TrustTier::Community => "community",
}
}
fn install_status_label(status: &InstallStatus) -> &'static str {
match status {
InstallStatus::Installed => "installed",
InstallStatus::Unchanged => "unchanged",
}
}