diff --git a/src/commands/artifact.rs b/src/commands/artifact.rs index 17d796f..9f6d612 100644 --- a/src/commands/artifact.rs +++ b/src/commands/artifact.rs @@ -441,23 +441,65 @@ pub async fn show_comment(client: &HotwiredClient, comment_id: &str) { } } -/// Resolve a comment -pub async fn resolve(client: &HotwiredClient, comment_id: &str) { +/// Reply to an existing comment (inherits path/selection from parent) +pub async fn reply_comment(client: &HotwiredClient, comment_id: &str, message: &str) { let state = validate::require_session(client).await; match client .request( - "artifact_resolve_comment", + "artifact_reply_comment", serde_json::json!({ "runId": state.run_id, "commentId": comment_id, - "resolvedBy": state.role_id, + "message": message, + "author": state.role_id, }), ) .await { Ok(response) if response.success => { - println!("Comment resolved: {}", comment_id); + let data = response.data.unwrap_or_default(); + let reply_id = data + .get("commentId") + .and_then(|v| v.as_str()) + .unwrap_or("?"); + println!("Reply added: {} (in thread of {})", reply_id, comment_id); + } + Ok(response) => { + eprintln!( + "error: {}", + response.error.unwrap_or_else(|| "unknown".into()) + ); + std::process::exit(1); + } + Err(e) => handle_error(e), + } +} + +/// Resolve a comment +pub async fn resolve(client: &HotwiredClient, comment_id: &str, reply: Option<&str>) { + let state = validate::require_session(client).await; + + let mut params = serde_json::json!({ + "runId": state.run_id, + "commentId": comment_id, + "resolvedBy": state.role_id, + }); + + if let Some(reply_msg) = reply { + params + .as_object_mut() + .unwrap() + .insert("reply".to_string(), serde_json::json!(reply_msg)); + } + + match client.request("artifact_resolve_comment", params).await { + Ok(response) if response.success => { + if reply.is_some() { + println!("Reply added and comment resolved: {}", comment_id); + } else { + println!("Comment resolved: {}", comment_id); + } } Ok(response) => { eprintln!( diff --git a/src/main.rs b/src/main.rs index c50c85d..b62be68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -317,13 +317,17 @@ enum ArtifactAction { /// Resolve a comment /// - /// Marks a comment as resolved. + /// Marks a comment as resolved. Optionally leave a closing reply. /// /// Examples: /// hotwired-cli artifact resolve cmt_abc123 + /// hotwired-cli artifact resolve cmt_abc123 --reply "Addressed in revision 3" Resolve { /// Comment ID comment_id: String, + /// Optional closing reply before resolving + #[arg(long)] + reply: Option, }, /// List all versions of an artifact @@ -378,6 +382,20 @@ enum CommentAction { reply_to: Option, }, + /// Reply to an existing comment + /// + /// Creates a threaded reply inheriting the parent's path and selection. + /// No need to specify path or target_text — they are inherited from the parent. + /// + /// Examples: + /// hotwired-cli artifact comment reply cmt_abc123 "I agree, will fix" + Reply { + /// Comment ID to reply to + comment_id: String, + /// Reply message + message: String, + }, + /// Show a specific comment and its thread /// /// Retrieves a comment by ID along with any replies in its thread. @@ -648,6 +666,12 @@ async fn main() -> Result<(), Box> { ) .await; } + CommentAction::Reply { + comment_id, + message, + } => { + commands::artifact::reply_comment(&client, &comment_id, &message).await; + } CommentAction::Show { comment_id } => { commands::artifact::show_comment(&client, &comment_id).await; } @@ -658,8 +682,8 @@ async fn main() -> Result<(), Box> { ArtifactAction::Comments { path, status } => { commands::artifact::list_comments(&client, &path, &status).await; } - ArtifactAction::Resolve { comment_id } => { - commands::artifact::resolve(&client, &comment_id).await; + ArtifactAction::Resolve { comment_id, reply } => { + commands::artifact::resolve(&client, &comment_id, reply.as_deref()).await; } ArtifactAction::Versions { path } => { commands::artifact::list_versions(&client, &path).await;