diff --git a/src/agent.rs b/src/agent.rs index 6d87093..9098d3e 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -327,6 +327,22 @@ async fn agent_loop( ensure_workspace(&exec, &workdir).await; let _ = tokio::fs::write(format!("{}/requirement.md", workdir), &requirement).await; + // Run template setup if present + if let Some(ref tid) = template_id { + let template_dir = if template::is_repo_template(tid) { + template::extract_repo_template(tid, mgr.template_repo.as_ref()) + .await + .ok() + } else { + Some(std::path::Path::new(template::templates_dir()).join(tid)) + }; + if let Some(ref tdir) = template_dir { + if let Err(e) = template::run_setup(tdir, &workdir).await { + tracing::error!("Template setup failed for {}: {}", tid, e); + } + } + } + let instructions = if let Some(ref t) = loaded_template { t.instructions.clone() } else { diff --git a/src/template.rs b/src/template.rs index 86bc4c2..312477c 100644 --- a/src/template.rs +++ b/src/template.rs @@ -402,6 +402,20 @@ pub async fn extract_repo_template(template_id: &str, repo_cfg: Option<&Template } } + // Make setup executable if present + let setup_file = dest.join("setup"); + if setup_file.is_file() { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Ok(meta) = tokio::fs::metadata(&setup_file).await { + let mut perms = meta.permissions(); + perms.set_mode(perms.mode() | 0o111); + let _ = tokio::fs::set_permissions(&setup_file, perms).await; + } + } + } + tracing::info!("Extracted repo template '{}' to {}", template_id, dest.display()); Ok(dest) } @@ -483,6 +497,44 @@ pub async fn select_template(llm: &LlmClient, requirement: &str, repo_cfg: Optio // --- Template loading --- +/// Run the template's `setup` executable with cwd set to workdir. +/// The template directory is read-only; setup runs in the workdir to initialize +/// the workspace environment (e.g. pulling binaries, installing deps). +pub async fn run_setup(template_dir: &Path, workdir: &str) -> anyhow::Result<()> { + let setup = template_dir.join("setup"); + if !setup.exists() { + return Ok(()); + } + + tracing::info!("Running template setup in workdir: {}", workdir); + let output = tokio::process::Command::new(&setup) + .current_dir(workdir) + .output() + .await + .map_err(|e| anyhow::anyhow!("Failed to run setup: {}", e))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !stdout.is_empty() { + tracing::info!("setup stdout: {}", stdout.trim_end()); + } + if !stderr.is_empty() { + tracing::warn!("setup stderr: {}", stderr.trim_end()); + } + + if !output.status.success() { + anyhow::bail!( + "Template setup failed (exit {}): {}", + output.status.code().unwrap_or(-1), + stderr.trim_end() + ); + } + + tracing::info!("Template setup completed successfully"); + Ok(()) +} + /// Copy template contents to workdir (excluding template.json, tools/, kb/, INSTRUCTIONS.md). pub async fn apply_template(template_dir: &Path, workdir: &str) -> anyhow::Result<()> { if !template_dir.is_dir() { @@ -507,6 +559,7 @@ async fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> { || name_str == "tools" || name_str == "kb" || name_str == "examples" + || name_str == "setup" || name_str == "INSTRUCTIONS.md") { continue;