diff --git a/crates/colibri-client/src/bin/colibri.rs b/crates/colibri-client/src/bin/colibri.rs index 1abe9b4..6a3e926 100644 --- a/crates/colibri-client/src/bin/colibri.rs +++ b/crates/colibri-client/src/bin/colibri.rs @@ -61,6 +61,8 @@ fn usage() -> &'static str { colibri [--socket PATH] list-tasks [--status STATUS] colibri [--socket PATH] create-task --title TEXT [--description TEXT] colibri [--socket PATH] intake-task --title TEXT [--description TEXT] [--capability CAP]... + colibri [--socket PATH] list-skills + colibri [--socket PATH] register-skill NAME [--description TEXT] [--category CAT] Socket defaults to COLIBRI_DAEMON_SOCKET, then the daemon's configured default. @@ -71,6 +73,8 @@ Examples: colibri create-task --title "verify OSA smoke" --description "manual follow-up" colibri intake-task --title "triage watchdog" --capability freebsd colibri list-tasks --status queued + colibri register-skill freebsd-smoke --description "Live USB smoke test" --category freebsd + colibri list-skills "# } @@ -155,6 +159,19 @@ where capabilities, }) } + "list-skills" => expect_arity(&args, 1).map(|()| Command::ListSkills), + "register-skill" => { + if args.len() < 2 { + Err("register-skill requires NAME\n\n".to_string() + usage()) + } else { + let (description, category) = parse_skill_options(&args[2..])?; + Ok(Command::RegisterSkill { + name: args[1].clone(), + description, + category, + }) + } + } other => Err(format!("unknown command: {other}\n\n{}", usage())), }?; @@ -305,6 +322,32 @@ fn parse_spawn_options(args: &[String]) -> Result<(Option, Option Result<(Option, Option), String> { + let mut description = None; + let mut category = None; + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--description" => { + let Some(value) = args.get(i + 1) else { + return Err("--description requires TEXT\n\n".to_string() + usage()); + }; + description = Some(value.clone()); + i += 2; + } + "--category" => { + let Some(value) = args.get(i + 1) else { + return Err("--category requires CAT\n\n".to_string() + usage()); + }; + category = Some(value.clone()); + i += 2; + } + other => return Err(format!("unknown register-skill option: {other}\n\n{}", usage())), + } + } + Ok((description, category)) +} + async fn run(options: Options) -> Result<(), ClientError> { let client = DaemonClient::new(options.socket_path); match options.command { @@ -335,6 +378,12 @@ async fn run(options: Options) -> Result<(), ClientError> { description, capabilities, } => print_json(&client.intake_task(title, description, capabilities).await?), + Command::ListSkills => print_json(&client.list_skills().await?), + Command::RegisterSkill { + name, + description, + category, + } => print_json(&client.register_skill(name, description, category).await?), } } diff --git a/crates/colibri-client/src/lib.rs b/crates/colibri-client/src/lib.rs index f85179f..ac51869 100644 --- a/crates/colibri-client/src/lib.rs +++ b/crates/colibri-client/src/lib.rs @@ -175,6 +175,24 @@ impl DaemonClient { }) .await } + + pub async fn list_skills(&self) -> Result { + self.request(&HerdrCommand::ListSkills).await + } + + pub async fn register_skill( + &self, + name: impl Into, + description: Option, + category: Option, + ) -> Result { + self.request(&HerdrCommand::RegisterSkill { + name: name.into(), + description, + category, + }) + .await + } } #[cfg(test)]