From 2bc211f267813120bb1c00d290b45b52e2c77ee9 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 14 Mar 2026 09:58:03 -0400 Subject: [PATCH] Add `fabro workflow list` command Adds a new `workflow` subcommand group with a `list` command that discovers available workflows via `fabro.toml` and prints their names. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crates/fabro-cli/src/main.rs | 19 ++++++++++ lib/crates/fabro-workflows/src/cli/mod.rs | 1 + .../fabro-workflows/src/cli/project_config.rs | 2 +- .../fabro-workflows/src/cli/workflow.rs | 37 +++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 lib/crates/fabro-workflows/src/cli/workflow.rs diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 378171013..cfa00d501 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -116,6 +116,11 @@ enum Command { #[command(subcommand)] command: SkillCommand, }, + /// Workflow operations + Workflow { + #[command(subcommand)] + command: WorkflowCommand, + }, /// System maintenance commands System { #[command(subcommand)] @@ -157,6 +162,12 @@ enum SkillCommand { Install(skill::SkillInstallArgs), } +#[derive(Subcommand)] +enum WorkflowCommand { + /// List available workflows + List(fabro_workflows::cli::workflow::WorkflowListArgs), +} + #[derive(Subcommand)] enum LlmCommand { /// Execute a prompt @@ -300,6 +311,9 @@ async fn main_inner() -> (String, Result<()>) { PrCommand::Merge(_) => "pr merge", PrCommand::Close(_) => "pr close", }, + Command::Workflow { command } => match command { + WorkflowCommand::List(_) => "workflow list", + }, Command::Skill { command } => match command { SkillCommand::Install(_) => "skill install", }, @@ -593,6 +607,11 @@ async fn main_inner() -> (String, Result<()>) { } } } + Command::Workflow { command } => match command { + WorkflowCommand::List(args) => { + fabro_workflows::cli::workflow::workflow_list_command(&args)?; + } + }, Command::Skill { command } => match command { SkillCommand::Install(args) => { skill::run_skill_install(&args)?; diff --git a/lib/crates/fabro-workflows/src/cli/mod.rs b/lib/crates/fabro-workflows/src/cli/mod.rs index 1625dd30f..d2d2f1e63 100644 --- a/lib/crates/fabro-workflows/src/cli/mod.rs +++ b/lib/crates/fabro-workflows/src/cli/mod.rs @@ -13,6 +13,7 @@ pub mod run_config; pub mod runs; pub mod ssh; pub mod validate; +pub mod workflow; use std::path::Path; diff --git a/lib/crates/fabro-workflows/src/cli/project_config.rs b/lib/crates/fabro-workflows/src/cli/project_config.rs index 4eda0f6da..b7478e63f 100644 --- a/lib/crates/fabro-workflows/src/cli/project_config.rs +++ b/lib/crates/fabro-workflows/src/cli/project_config.rs @@ -188,7 +188,7 @@ fn list_workflows_in(workflows_dir: &Path) -> Vec { /// List workflow names by scanning project and user workflow directories. /// Project workflows appear first; user workflows are deduplicated. -fn list_available_workflows( +pub fn list_available_workflows( project_workflows_dir: Option<&Path>, user_workflows_dir: Option<&Path>, ) -> Vec { diff --git a/lib/crates/fabro-workflows/src/cli/workflow.rs b/lib/crates/fabro-workflows/src/cli/workflow.rs new file mode 100644 index 000000000..13ae42c95 --- /dev/null +++ b/lib/crates/fabro-workflows/src/cli/workflow.rs @@ -0,0 +1,37 @@ +use anyhow::bail; +use clap::Args; + +use super::project_config::{ + discover_project_config, list_available_workflows, resolve_fabro_root, +}; + +#[derive(Args)] +pub struct WorkflowListArgs {} + +pub fn workflow_list_command(_args: &WorkflowListArgs) -> anyhow::Result<()> { + let cwd = std::env::current_dir()?; + + let (config_path, config) = match discover_project_config(&cwd)? { + Some(found) => found, + None => bail!( + "No fabro.toml found in {cwd} or any parent directory", + cwd = cwd.display() + ), + }; + + let fabro_root = resolve_fabro_root(&config_path, &config); + let project_wf_dir = fabro_root.join("workflows"); + let user_wf_dir = dirs::home_dir().map(|h| h.join(".fabro").join("workflows")); + + let workflows = list_available_workflows(Some(&project_wf_dir), user_wf_dir.as_deref()); + + if workflows.is_empty() { + eprintln!("No workflows found"); + } else { + for name in &workflows { + println!("{name}"); + } + } + + Ok(()) +}