diff --git a/lib/crates/fabro-cli/src/install.rs b/lib/crates/fabro-cli/src/install.rs index fcf51a0c2..a401a0aa3 100644 --- a/lib/crates/fabro-cli/src/install.rs +++ b/lib/crates/fabro-cli/src/install.rs @@ -258,9 +258,33 @@ struct CallbackParams { code: String, } +fn build_github_app_manifest(app_name: &str, port: u16, web_url: &str) -> serde_json::Value { + serde_json::json!({ + "name": app_name, + "url": "https://github.com/apps/arc", + "redirect_url": format!("http://127.0.0.1:{port}/callback"), + "callback_urls": [format!("{web_url}/auth/callback")], + "setup_url": format!("{web_url}/setup/callback"), + "public": false, + "default_permissions": { + "contents": "write", + "metadata": "read", + "pull_requests": "write", + "checks": "write", + "issues": "write", + "emails": "read" + }, + "default_events": [] + }) +} + /// Run the GitHub App manifest registration flow via a temporary local server. /// Returns env var pairs (key, value) for secrets to merge into `.env`. -async fn setup_github_app(arc_dir: &Path, s: &Styles) -> Result> { +async fn setup_github_app( + arc_dir: &Path, + s: &Styles, + web_url: &str, +) -> Result> { // Random suffix so app names don't collide let mut rng = rand::thread_rng(); let suffix: String = (0..6) @@ -275,21 +299,7 @@ async fn setup_github_app(arc_dir: &Path, s: &Styles) -> Result Result Result<()> { +pub async fn run_install(web_url: &str) -> Result<()> { let s = Styles::detect_stderr(); let emoji = console::Emoji("⚒️ ", ""); @@ -640,7 +650,7 @@ pub async fn run_install() -> Result<()> { .await??; if setup_github { - let github_env_pairs = setup_github_app(&arc_dir, &s).await?; + let github_env_pairs = setup_github_app(&arc_dir, &s, web_url).await?; let slug = { let cli_toml_path = arc_dir.join("cli.toml"); let toml_content = std::fs::read_to_string(&cli_toml_path).unwrap_or_default(); @@ -951,4 +961,21 @@ mod tests { assert_eq!(tls.key, PathBuf::from("~/.fabro/certs/server.key")); assert_eq!(tls.ca, PathBuf::from("~/.fabro/certs/ca.crt")); } + + // -- GitHub App manifest -- + + #[test] + fn manifest_includes_callback_urls_and_setup_url() { + let web_url = "https://app.example.com"; + let manifest = build_github_app_manifest("Arc-test", 12345, web_url); + + assert_eq!( + manifest["callback_urls"], + serde_json::json!(["https://app.example.com/auth/callback"]), + ); + assert_eq!( + manifest["setup_url"], + serde_json::json!("https://app.example.com/setup/callback"), + ); + } } diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 543ffe2b9..8eef1ab42 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -119,7 +119,11 @@ enum Command { #[command(hide = true)] Init, /// Set up the Fabro environment (LLMs, certs, GitHub) - Install, + Install { + /// Base URL for the web UI (used for OAuth callback URLs) + #[arg(long, default_value = "http://localhost:5173")] + web_url: String, + }, /// List workflow runs #[command(hide = true)] Ps(commands::runs::RunsListArgs), @@ -490,7 +494,7 @@ async fn main_inner() -> (String, Result<()>) { RepoCommand::Deinit => "repo deinit", }, Command::Init => "init", - Command::Install => "install", + Command::Install { .. } => "install", Command::Ps(_) => "ps", Command::Rm(_) => "rm", Command::Pr { command } => match command { @@ -572,7 +576,7 @@ async fn main_inner() -> (String, Result<()>) { | Command::Exec(_) | Command::Repo { .. } | Command::Init - | Command::Install + | Command::Install { .. } ) { upgrade::spawn_upgrade_check(cli.no_upgrade_check, upgrade_check_enabled) } else { @@ -845,8 +849,8 @@ async fn main_inner() -> (String, Result<()>) { ); init::run_init().await?; } - Command::Install => { - install::run_install().await?; + Command::Install { web_url } => { + install::run_install(&web_url).await?; } Command::Ps(args) => { let styles = fabro_util::terminal::Styles::detect_stdout();