bamboo_server/handlers/command/
handlers.rs1use actix_web::{web, HttpResponse};
2
3use crate::app_state::AppState;
4use crate::error::AppError;
5use crate::handlers::settings::is_safe_workflow_name;
6
7use super::sources::{list_mcp_tools_as_commands, list_workflows_as_commands, skill_to_command};
8use super::types::CommandListResponse;
9
10pub async fn list_commands(app_state: web::Data<AppState>) -> Result<HttpResponse, AppError> {
12 let mut commands = Vec::new();
13
14 match list_workflows_as_commands(&app_state.app_data_dir).await {
15 Ok(workflows) => commands.extend(workflows),
16 Err(error) => {
17 tracing::warn!("Failed to load workflows: {error}");
18 }
19 }
20
21 let skills = app_state
22 .skill_manager
23 .store()
24 .list_skills(None, false)
25 .await;
26 let skill_commands = skills.into_iter().map(|skill| skill_to_command(&skill));
27 commands.extend(skill_commands);
28
29 match list_mcp_tools_as_commands(app_state.get_ref()).await {
30 Ok(mcp_tools) => commands.extend(mcp_tools),
31 Err(error) => {
32 tracing::warn!("Failed to load MCP tools: {error}");
33 }
34 }
35
36 commands.sort_by(|left, right| left.name.cmp(&right.name));
37 Ok(HttpResponse::Ok().json(CommandListResponse {
38 total: commands.len(),
39 commands,
40 }))
41}
42
43pub async fn get_command(
45 app_state: web::Data<AppState>,
46 path: web::Path<(String, String)>,
47) -> Result<HttpResponse, AppError> {
48 let (command_type, id) = path.into_inner();
49
50 match command_type.as_str() {
51 "workflow" => {
52 if !is_safe_workflow_name(&id) {
53 return Err(AppError::BadRequest("Invalid workflow name".to_string()));
54 }
55
56 let workflows_dir = app_state.app_data_dir.join("workflows");
57 let filename = format!("{id}.md");
58 let filepath = workflows_dir.join(&filename);
59
60 if !filepath.exists() {
61 return Err(AppError::NotFound(format!("Workflow {id} not found")));
62 }
63
64 let content = tokio::fs::read_to_string(&filepath)
65 .await
66 .map_err(|error| {
67 AppError::InternalError(anyhow::anyhow!("Failed to read workflow: {error}"))
68 })?;
69
70 Ok(HttpResponse::Ok().json(serde_json::json!({
71 "id": format!("workflow-{id}"),
72 "name": id,
73 "content": content,
74 "type": "workflow"
75 })))
76 }
77 "skill" => match app_state.skill_manager.store().get_skill(&id).await {
78 Ok(skill) => Ok(HttpResponse::Ok().json(skill)),
79 Err(error) => Err(AppError::NotFound(format!("Skill {id} not found: {error}"))),
80 },
81 "mcp" => Err(AppError::NotFound(
82 "MCP tools do not support content retrieval".to_string(),
83 )),
84 _ => Err(AppError::NotFound(format!(
85 "Unknown command type: {command_type}"
86 ))),
87 }
88}
89
90pub fn config(cfg: &mut web::ServiceConfig) {
92 cfg.route("/commands", web::get().to(list_commands))
93 .route("/commands/{command_type}/{id}", web::get().to(get_command));
94}