Add CLI comment, tag, and blocker commands
Assisted-by: Claude Opus 4.6 via pi
diff --git a/crates/ranger-cli/src/commands/blocker.rs b/crates/ranger-cli/src/commands/blocker.rs
new file mode 100644
index 0000000..a6bebfa
--- /dev/null
+++ b/crates/ranger-cli/src/commands/blocker.rs
@@ -0,0 +1,48 @@
+use clap::Subcommand;
+use ranger_lib::db::SqlitePool;
+use ranger_lib::ops;
+
+use crate::output;
+
+#[derive(Subcommand)]
+pub enum BlockerCommands {
+ /// Add a blocker to a task
+ Add {
+ /// Task key or prefix (the blocked task)
+ task: String,
+ /// Blocking task key or prefix
+ blocked_by: String,
+ },
+ /// Remove a blocker from a task
+ Remove {
+ /// Task key or prefix (the blocked task)
+ task: String,
+ /// Blocking task key or prefix
+ blocked_by: String,
+ },
+}
+
+pub async fn run(pool: &SqlitePool, command: BlockerCommands, json: bool) -> anyhow::Result<()> {
+ match command {
+ BlockerCommands::Add { task, blocked_by } => {
+ let t = ops::task::get_by_key_prefix(pool, &task).await?;
+ let bt = ops::task::get_by_key_prefix(pool, &blocked_by).await?;
+ let blocker = ops::blocker::add(pool, t.id, bt.id).await?;
+ output::print(&blocker, json, |_| {
+ println!(
+ "{} blocked by {} {}",
+ &t.key[..8],
+ &bt.key[..8],
+ bt.title
+ );
+ });
+ }
+ BlockerCommands::Remove { task, blocked_by } => {
+ let t = ops::task::get_by_key_prefix(pool, &task).await?;
+ let bt = ops::task::get_by_key_prefix(pool, &blocked_by).await?;
+ ops::blocker::remove(pool, t.id, bt.id).await?;
+ println!("Removed blocker {} from {}", &bt.key[..8], &t.key[..8]);
+ }
+ }
+ Ok(())
+}
diff --git a/crates/ranger-cli/src/commands/comment.rs b/crates/ranger-cli/src/commands/comment.rs
new file mode 100644
index 0000000..134822f
--- /dev/null
+++ b/crates/ranger-cli/src/commands/comment.rs
@@ -0,0 +1,41 @@
+use clap::Subcommand;
+use ranger_lib::db::SqlitePool;
+use ranger_lib::ops;
+
+use crate::output;
+
+#[derive(Subcommand)]
+pub enum CommentCommands {
+ /// Add a comment to a task
+ Add {
+ /// Task key or prefix
+ task: String,
+ /// Comment body
+ body: String,
+ },
+ /// List comments on a task
+ List {
+ /// Task key or prefix
+ task: String,
+ },
+}
+
+pub async fn run(pool: &SqlitePool, command: CommentCommands, json: bool) -> anyhow::Result<()> {
+ match command {
+ CommentCommands::Add { task, body } => {
+ let t = ops::task::get_by_key_prefix(pool, &task).await?;
+ let comment = ops::comment::add(pool, t.id, &body).await?;
+ output::print(&comment, json, |c| {
+ println!("[{}] {}", c.created_at, c.body);
+ });
+ }
+ CommentCommands::List { task } => {
+ let t = ops::task::get_by_key_prefix(pool, &task).await?;
+ let comments = ops::comment::list(pool, t.id).await?;
+ output::print_list(&comments, json, |c| {
+ println!("[{}] {}", c.created_at, c.body);
+ });
+ }
+ }
+ Ok(())
+}
diff --git a/crates/ranger-cli/src/commands/mod.rs b/crates/ranger-cli/src/commands/mod.rs
index 5381e93..3674cef 100644
--- a/crates/ranger-cli/src/commands/mod.rs
+++ b/crates/ranger-cli/src/commands/mod.rs
@@ -1,2 +1,5 @@
pub mod backlog;
+pub mod blocker;
+pub mod comment;
+pub mod tag;
pub mod task;
diff --git a/crates/ranger-cli/src/commands/tag.rs b/crates/ranger-cli/src/commands/tag.rs
new file mode 100644
index 0000000..8c87496
--- /dev/null
+++ b/crates/ranger-cli/src/commands/tag.rs
@@ -0,0 +1,23 @@
+use clap::Subcommand;
+use ranger_lib::db::SqlitePool;
+use ranger_lib::ops;
+
+use crate::output;
+
+#[derive(Subcommand)]
+pub enum TagCommands {
+ /// List all tags
+ List,
+}
+
+pub async fn run(pool: &SqlitePool, command: TagCommands, json: bool) -> anyhow::Result<()> {
+ match command {
+ TagCommands::List => {
+ let tags = ops::tag::list(pool).await?;
+ output::print_list(&tags, json, |t| {
+ println!("{}", t.name);
+ });
+ }
+ }
+ Ok(())
+}
diff --git a/crates/ranger-cli/src/main.rs b/crates/ranger-cli/src/main.rs
index 53cf556..0fa6f48 100644
--- a/crates/ranger-cli/src/main.rs
+++ b/crates/ranger-cli/src/main.rs
@@ -31,6 +31,21 @@ enum Commands {
#[command(subcommand)]
command: commands::task::TaskCommands,
},
+ /// Manage comments
+ Comment {
+ #[command(subcommand)]
+ command: commands::comment::CommentCommands,
+ },
+ /// Manage tags
+ Tag {
+ #[command(subcommand)]
+ command: commands::tag::TagCommands,
+ },
+ /// Manage blockers
+ Blocker {
+ #[command(subcommand)]
+ command: commands::blocker::BlockerCommands,
+ },
}
fn resolve_db_path(cli_path: Option<PathBuf>) -> PathBuf {
@@ -55,6 +70,15 @@ async fn main() -> anyhow::Result<()> {
Commands::Task { command } => {
commands::task::run(&pool, command, cli.json).await?;
}
+ Commands::Comment { command } => {
+ commands::comment::run(&pool, command, cli.json).await?;
+ }
+ Commands::Tag { command } => {
+ commands::tag::run(&pool, command, cli.json).await?;
+ }
+ Commands::Blocker { command } => {
+ commands::blocker::run(&pool, command, cli.json).await?;
+ }
}
Ok(())