use std::env;
use netsky_prompts::skills::{InjectFormat, SkillSummary, inject_skills, list_skills, read_skill};
use serde_json::json;
use crate::cli::{SkillCommand, SkillInjectFormat};
pub fn run(cmd: SkillCommand) -> netsky_core::Result<()> {
match cmd {
SkillCommand::Ls { json, verbose } => list(json, verbose),
SkillCommand::Cat { name } => cat(&name),
SkillCommand::Inject { csv, format } => inject(&csv, format),
}
}
fn list(json_output: bool, verbose: bool) -> netsky_core::Result<()> {
let cwd = env::current_dir()?;
let skills = prompt_result(list_skills(&cwd))?;
if json_output {
println!("{}", serde_json::to_string_pretty(&skills)?);
return Ok(());
}
print_list(&skills, verbose);
Ok(())
}
fn cat(name: &str) -> netsky_core::Result<()> {
let cwd = env::current_dir()?;
let doc = prompt_result(read_skill(&cwd, name))?;
print!("{}", doc.body);
Ok(())
}
fn inject(csv: &str, format: SkillInjectFormat) -> netsky_core::Result<()> {
let cwd = env::current_dir()?;
let names = parse_csv(csv);
if format == SkillInjectFormat::Json {
let injected = prompt_result(inject_skills(&cwd, &names, InjectFormat::Plain))?;
let envelope = json!({
"names": injected.names,
"bytes": injected.bytes,
"estimated_tokens": injected.estimated_tokens,
"text": injected.text,
});
println!("{}", serde_json::to_string_pretty(&envelope)?);
return Ok(());
}
let injected = prompt_result(inject_skills(&cwd, &names, to_inject_format(format)))?;
print!("{}", injected.text);
Ok(())
}
fn prompt_result<T>(result: anyhow::Result<T>) -> netsky_core::Result<T> {
result.map_err(|err| netsky_core::Error::msg(err.to_string()))
}
fn parse_csv(csv: &str) -> Vec<String> {
csv.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
.map(ToOwned::to_owned)
.collect()
}
fn to_inject_format(format: SkillInjectFormat) -> InjectFormat {
match format {
SkillInjectFormat::Plain => InjectFormat::Plain,
SkillInjectFormat::Claude => InjectFormat::Claude,
SkillInjectFormat::Codex => InjectFormat::Codex,
SkillInjectFormat::Json => InjectFormat::Plain,
}
}
fn print_list(skills: &[SkillSummary], verbose: bool) {
if skills.is_empty() {
return;
}
if verbose {
let name_width = skills
.iter()
.map(|skill| skill.name.len())
.max()
.unwrap_or(4);
for skill in skills {
println!(
"{name:<name_width$} {tokens:>6} tok {bytes:>6} B {support:>2} support {path} {description}",
name = skill.name,
tokens = skill.estimated_tokens,
bytes = skill.bytes,
support = skill.support_files.len(),
path = skill.path.display(),
description = skill.description,
);
}
return;
}
let name_width = skills
.iter()
.map(|skill| skill.name.len())
.max()
.unwrap_or(4);
for skill in skills {
println!(
"{name:<name_width$} {tokens:>6} tok {bytes:>6} B {description}",
name = skill.name,
tokens = skill.estimated_tokens,
bytes = skill.bytes,
description = skill.description,
);
}
}