//! Git credential management tool. //! //! Provides store, list, and erase actions for git credentials //! by communicating with `git credential approve/reject` via stdin. use crate::tools::{execute_cmd, Tool, ToolCtx}; use anyhow::Result; use serde_json::{json, Value}; use std::io::Write; use std::process::{Command, Stdio}; use tracing::{info, instrument}; /// Tool that manages git credentials (store, list, erase). /// /// Interacts with the git credential helper protocol via `git credential approve` /// (for storing) and `git credential reject` (for erasing). The `list` action /// reads the global git config. pub struct GitCred; impl Tool for GitCred { fn name(&self) -> &'static str { "git_cred" } fn description(&self) -> &'static str { "Manage git credentials (store, retrieve, list)" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "action": { "type": "string", "enum": ["store", "list", "erase"], "description": "Credential action to perform" }, "url": { "type": "string", "description": "Git URL for the credential" }, "username": { "type": "string", "description": "Username for authentication" }, "password": { "type": "string", "description": "Password or token for authentication" } }, "required": ["action"] }) } #[instrument(skip(self, _ctx, args))] fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result { let action = crate::tools::arg_str(args, "action")?; info!(action, "git_cred invoked"); match action.as_str() { "store" => { let url = crate::tools::arg_str(args, "url")?; let username = crate::tools::arg_str(args, "username")?; let password = crate::tools::arg_str(args, "password")?; let input = format!("url={url}\nusername={username}\npassword={password}\n"); let mut child = Command::new("git") .args(["credential", "approve"]) .stdin(Stdio::piped()) .spawn()?; if let Some(ref mut stdin) = child.stdin { stdin.write_all(input.as_bytes())?; } child.wait()?; info!("credential stored for {url}"); Ok(format!("Credential stored for {url}")) } "list" => { let output = execute_cmd( std::process::Command::new("git").args(["config", "--global", "--list"]), )?; Ok(output) } "erase" => { let url = crate::tools::arg_str(args, "url")?; let input = format!("url={url}\n"); let mut child = Command::new("git") .args(["credential", "reject"]) .stdin(Stdio::piped()) .spawn()?; if let Some(ref mut stdin) = child.stdin { stdin.write_all(input.as_bytes())?; } child.wait()?; info!("credential erased for {url}"); Ok(format!("Credential erased for {url}")) } _ => anyhow::bail!("unknown action: {}", action), } } }