Skip to content

Commit 2a6042d

Browse files
committed
fix(cli): make skills show human-readable without --json; add JSON contract test
1 parent 07e54c7 commit 2a6042d

1 file changed

Lines changed: 81 additions & 3 deletions

File tree

  • crates/loopforge-cli/src/dispatch/skills

crates/loopforge-cli/src/dispatch/skills/listing.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use crate::skills;
55
pub(super) fn run_list(workspace: PathBuf, json: bool) -> anyhow::Result<()> {
66
let list = skills::list_skills(&workspace)?;
77
if json {
8-
println!("{}", serde_json::to_string_pretty(&list)?);
8+
println!(
9+
"{}",
10+
serde_json::to_string_pretty(&build_skills_list_json(&list)?)?
11+
);
912
} else if list.is_empty() {
1013
println!("no skills discovered");
1114
} else {
@@ -39,7 +42,82 @@ pub(super) fn run_show(name: String, workspace: PathBuf, json: bool) -> anyhow::
3942
}))
4043
.collect::<Vec<_>>(),
4144
});
42-
println!("{}", serde_json::to_string_pretty(&item)?);
43-
if !json {}
45+
if json {
46+
println!("{}", serde_json::to_string_pretty(&item)?);
47+
} else {
48+
println!("name: {}", item["name"].as_str().unwrap_or("-"));
49+
println!("version: {}", item["version"].as_str().unwrap_or("-"));
50+
println!("source: {}", item["source"].as_str().unwrap_or("-"));
51+
println!("root_dir: {}", item["root_dir"].as_str().unwrap_or("-"));
52+
println!(
53+
"manifest_path: {}",
54+
item["manifest_path"].as_str().unwrap_or("-")
55+
);
56+
println!("entry: {}", item["entry"].as_str().unwrap_or("-"));
57+
let perms: Vec<String> = item["permissions"]
58+
.as_array()
59+
.into_iter()
60+
.flatten()
61+
.filter_map(|v| v.as_str().map(|s| s.to_string()))
62+
.collect();
63+
println!(
64+
"permissions: {}",
65+
if perms.is_empty() {
66+
"(none)".to_string()
67+
} else {
68+
perms.join(", ")
69+
}
70+
);
71+
72+
let deps = item["dependencies"].as_array().cloned().unwrap_or_default();
73+
if deps.is_empty() {
74+
println!("dependencies: (none)");
75+
} else {
76+
println!("dependencies:");
77+
for dep in deps {
78+
let name = dep.get("name").and_then(|v| v.as_str()).unwrap_or("-");
79+
let version_req = dep
80+
.get("version_req")
81+
.and_then(|v| v.as_str())
82+
.unwrap_or("-");
83+
println!("- {name} {version_req}");
84+
}
85+
}
86+
}
4487
Ok(())
4588
}
89+
90+
fn build_skills_list_json(list: &[skills::SkillListItem]) -> anyhow::Result<serde_json::Value> {
91+
Ok(serde_json::to_value(list)?)
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use super::*;
97+
use serde_json::json;
98+
99+
#[test]
100+
fn build_skills_list_json_keeps_expected_shape() {
101+
let list = vec![skills::SkillListItem {
102+
name: "alpha".to_string(),
103+
version: "1.2.3".to_string(),
104+
source: "workspace".to_string(),
105+
root_dir: "/tmp/alpha".to_string(),
106+
entry_path: "/tmp/alpha/SKILL.md".to_string(),
107+
permissions: vec!["readonly".to_string()],
108+
}];
109+
110+
let out = build_skills_list_json(&list).unwrap();
111+
assert_eq!(
112+
out,
113+
json!([{
114+
"name": "alpha",
115+
"version": "1.2.3",
116+
"source": "workspace",
117+
"root_dir": "/tmp/alpha",
118+
"entry_path": "/tmp/alpha/SKILL.md",
119+
"permissions": ["readonly"],
120+
}])
121+
);
122+
}
123+
}

0 commit comments

Comments
 (0)