| 1 | use { |
| 2 | crate::{ |
| 3 | cli::RepoCommand, |
| 4 | context::Ctx, |
| 5 | ui::{self, prompt}, |
| 6 | }, |
| 7 | anyhow::Result, |
| 8 | sigit_si_api::NewRepository, |
| 9 | }; |
| 10 | |
| 11 | pub async fn run(ctx: &Ctx, command: RepoCommand) -> Result<()> { |
| 12 | ctx.require_token()?; |
| 13 | |
| 14 | match command { |
| 15 | RepoCommand::List => list(ctx).await, |
| 16 | RepoCommand::Search { query, limit } => search(ctx, query, limit).await, |
| 17 | RepoCommand::View { repo } => view(ctx, repo).await, |
| 18 | RepoCommand::Create { |
| 19 | name, |
| 20 | private, |
| 21 | description, |
| 22 | yes, |
| 23 | } => create(ctx, name, private, description, yes).await, |
| 24 | RepoCommand::Browse { repo } => browse(ctx, repo).await, |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | async fn list(ctx: &Ctx) -> Result<()> { |
| 29 | let repos = ctx.client.repos().await?; |
| 30 | ui::emit(ctx.json, &repos, |repos| ui::render::repos(repos)) |
| 31 | } |
| 32 | |
| 33 | async fn search(ctx: &Ctx, query: Option<String>, limit: u32) -> Result<()> { |
| 34 | let repos = ctx |
| 35 | .client |
| 36 | .search_repos(query.as_deref(), Some(limit)) |
| 37 | .await?; |
| 38 | ui::emit(ctx.json, &repos, |repos| ui::render::repo_summaries(repos)) |
| 39 | } |
| 40 | |
| 41 | async fn view(ctx: &Ctx, repo: Option<String>) -> Result<()> { |
| 42 | let slug = ctx.resolve_repo(repo)?; |
| 43 | |
| 44 | // `GET /api/v1/repos` only lists the signed-in user's own repos, but |
| 45 | // `view` should also work on repos owned by someone else — so try the |
| 46 | // owned list first (it has the fuller `Repository` shape) and fall back |
| 47 | // to the cross-user MCP search. |
| 48 | if let Some(repo) = ctx |
| 49 | .client |
| 50 | .repos() |
| 51 | .await? |
| 52 | .into_iter() |
| 53 | .find(|r| r.full_name.eq_ignore_ascii_case(&slug.to_string())) |
| 54 | { |
| 55 | return ui::emit(ctx.json, &repo, ui::render::repo_detail); |
| 56 | } |
| 57 | |
| 58 | let matches = ctx.client.search_repos(Some(&slug.name), None).await?; |
| 59 | let summary = matches |
| 60 | .into_iter() |
| 61 | .find(|r| r.full_name.eq_ignore_ascii_case(&slug.to_string())) |
| 62 | .ok_or_else(|| anyhow::anyhow!("Repository {slug} was not found."))?; |
| 63 | ui::emit(ctx.json, &summary, |summary| { |
| 64 | ui::render::repo_summaries(std::slice::from_ref(summary)) |
| 65 | }) |
| 66 | } |
| 67 | |
| 68 | async fn create( |
| 69 | ctx: &Ctx, |
| 70 | name: String, |
| 71 | private: bool, |
| 72 | description: Option<String>, |
| 73 | yes: bool, |
| 74 | ) -> Result<()> { |
| 75 | if !yes { |
| 76 | let confirmed = prompt::confirm( |
| 77 | &format!( |
| 78 | "Create {} repository {}?", |
| 79 | if private { "private" } else { "public" }, |
| 80 | ui::highlight(&name) |
| 81 | ), |
| 82 | true, |
| 83 | )?; |
| 84 | if !confirmed { |
| 85 | ui::note("Cancelled."); |
| 86 | return Ok(()); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | let new_repo = NewRepository::new(&name) |
| 91 | .private(private) |
| 92 | .description(description); |
| 93 | let repo = ctx.client.create_repo(&new_repo).await?; |
| 94 | |
| 95 | ui::emit(ctx.json, &repo, |repo| { |
| 96 | ui::success(format!("Created {}.", ui::highlight(&repo.full_name))); |
| 97 | println!(" {}", repo.clone_url); |
| 98 | }) |
| 99 | } |
| 100 | |
| 101 | async fn browse(ctx: &Ctx, repo: Option<String>) -> Result<()> { |
| 102 | let slug = ctx.resolve_repo(repo)?; |
| 103 | let url = format!("{}/{slug}", ctx.client.base_url()); |
| 104 | open::that(&url)?; |
| 105 | ui::note(format!("Opened {url}")); |
| 106 | Ok(()) |
| 107 | } |