Skip to main content

parley/
lib.rs

1pub mod cli;
2pub mod domain;
3pub mod git;
4pub mod mcp;
5pub mod persistence;
6pub mod services;
7pub mod tui;
8
9use anyhow::{Context, Result, anyhow};
10use structopt::StructOpt;
11
12use crate::{
13    cli::{Cli, Command, ReviewCommand},
14    domain::review::ReviewState,
15    git::review_name::resolve_tui_review_name,
16    persistence::store::Store,
17    services::{
18        ai_session::{RunAiSessionInput, default_ai_session_mode, run_ai_session},
19        review_service::{AddCommentInput, AddReplyInput, ReviewService},
20    },
21};
22
23pub async fn run() -> Result<()> {
24    let cli = Cli::from_args();
25
26    let project_root =
27        std::env::current_dir().context("failed to read current working directory")?;
28    let store = Store::from_project_root(&project_root);
29    store.ensure_dirs().await?;
30    let service = ReviewService::new(store);
31
32    match cli.command {
33        Command::Tui {
34            review,
35            theme,
36            no_mouse,
37        } => {
38            let review = resolve_default_review_for_tui(&service, review.as_deref()).await?;
39            tui::run_tui(service, review, theme, no_mouse).await?;
40        }
41        Command::Review { command } => {
42            handle_review_command(command, &service).await?;
43        }
44        Command::Mcp => {
45            mcp::run_mcp(service).await?;
46        }
47    }
48
49    Ok(())
50}
51
52async fn resolve_default_review_for_tui(
53    service: &ReviewService,
54    explicit: Option<&str>,
55) -> Result<String> {
56    let resolved = resolve_tui_review_name(explicit)?;
57    if explicit.is_some() {
58        return Ok(resolved);
59    }
60
61    if service.load_review(&resolved).await.is_ok() {
62        return Ok(resolved);
63    }
64
65    let existing = service.list_reviews().await?;
66    if existing.len() == 1 {
67        return Ok(existing[0].clone());
68    }
69
70    Ok(resolved)
71}
72
73async fn handle_review_command(command: ReviewCommand, service: &ReviewService) -> Result<()> {
74    match command {
75        ReviewCommand::Create { name } => {
76            let review = service.create_review(&name).await?;
77            println!("created review {} in {:?}", review.name, review.state);
78        }
79        ReviewCommand::Start { name } => {
80            let review = service.set_state(&name, ReviewState::UnderReview).await?;
81            println!("review {} started in {:?}", review.name, review.state);
82        }
83        ReviewCommand::List => {
84            for review_name in service.list_reviews().await? {
85                println!("{review_name}");
86            }
87        }
88        ReviewCommand::Show { name, json } => {
89            let review = service.load_review(&name).await?;
90            if json {
91                println!("{}", serde_json::to_string_pretty(&review)?);
92            } else {
93                println!("name: {}", review.name);
94                println!("state: {:?}", review.state);
95                println!("comments: {}", review.comments.len());
96                for comment in review.comments {
97                    println!(
98                        "  #{} [{}] {}:{} {}",
99                        comment.id,
100                        match comment.status {
101                            crate::domain::review::CommentStatus::Open => "open",
102                            crate::domain::review::CommentStatus::Pending => "pending_human",
103                            crate::domain::review::CommentStatus::Addressed => "addressed",
104                        },
105                        comment
106                            .old_line
107                            .map(|value| value.to_string())
108                            .unwrap_or_else(|| "_".into()),
109                        comment
110                            .new_line
111                            .map(|value| value.to_string())
112                            .unwrap_or_else(|| "_".into()),
113                        comment.body
114                    );
115                }
116            }
117        }
118        ReviewCommand::SetState { name, state } => {
119            let review = service.set_state(&name, state.0).await?;
120            println!("state updated to {:?}", review.state);
121        }
122        ReviewCommand::AddComment {
123            name,
124            file,
125            side,
126            old_line,
127            new_line,
128            body,
129            author,
130        } => {
131            if old_line.is_none() && new_line.is_none() {
132                return Err(anyhow!("provide --old-line or --new-line"));
133            }
134
135            let review = service
136                .add_comment(
137                    &name,
138                    AddCommentInput {
139                        file_path: file,
140                        old_line,
141                        new_line,
142                        side: side.0,
143                        body,
144                        author: author.0,
145                    },
146                )
147                .await?;
148            println!("comment added. total comments: {}", review.comments.len());
149        }
150        ReviewCommand::AddReply {
151            name,
152            comment_id,
153            body,
154            author,
155        } => {
156            service
157                .add_reply(
158                    &name,
159                    AddReplyInput {
160                        comment_id,
161                        author: author.0,
162                        body,
163                    },
164                )
165                .await?;
166            println!("reply added to comment #{comment_id}");
167        }
168        ReviewCommand::MarkAddressed {
169            name,
170            comment_id,
171            author,
172        } => {
173            service.mark_addressed(&name, comment_id, author.0).await?;
174            println!("comment #{comment_id} marked addressed");
175        }
176        ReviewCommand::MarkOpen {
177            name,
178            comment_id,
179            author,
180        } => {
181            service.mark_open(&name, comment_id, author.0).await?;
182            println!("comment #{comment_id} marked open");
183        }
184        ReviewCommand::Done { name } => {
185            service.set_state(&name, ReviewState::Done).await?;
186            println!("review {name} marked done");
187        }
188        ReviewCommand::Resolve { name } => {
189            service.set_state(&name, ReviewState::Done).await?;
190            println!("review {name} resolved");
191        }
192        ReviewCommand::RunAiSession {
193            name,
194            provider,
195            mode,
196            comment_ids,
197        } => {
198            let mode = mode
199                .map(|value| value.0)
200                .unwrap_or_else(|| default_ai_session_mode(&comment_ids));
201            let result = run_ai_session(
202                service,
203                RunAiSessionInput {
204                    review_name: name,
205                    provider: provider.0,
206                    comment_ids,
207                    mode,
208                },
209            )
210            .await?;
211            println!("{}", serde_json::to_string_pretty(&result)?);
212        }
213    }
214
215    Ok(())
216}