//! `swarmctl agent agents-md` — read an agent's `AGENTS.md` and edit it with //! a diff, through swarm-controller's `/api/agents/{name}/agents-md` routes. //! //! The read is the copy the agent's harness last published, so it works //! while the agent is stopped. An edit is applied by the harness to the file //! itself, so it needs the agent running. use std::path::Path; use anyhow::{Context as _, Result}; use swarm_controller_api::agents_md::{AgentsMdEdit, AgentsMdSaved, AgentsMdView}; use crate::agent::{parse_ident, send}; use crate::remote::Target; fn runtime() -> Result { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .context("starting a tokio runtime for the controller request") } /// The agent's `AGENTS.md` path. The name is checked first, so it cannot /// reshape the URI. fn member(agent: &str) -> Result { let agent = parse_ident(agent, "agent name")?; Ok(format!("/api/agents/{agent}/agents-md")) } /// Run `swarmctl agent agents-md get`: the text on stdout, unchanged, and its /// revision on stderr, so stdout redirected to a file is the file. pub(crate) fn get(target: &Target, agent: &str) -> Result<()> { let uri = member(agent)?; let view: AgentsMdView = runtime()?.block_on(send( target, hyper::Method::GET, &uri, None::<&()>, "agents-md-get", ))?; eprintln!("revision {}", view.revision); print!("{}", view.content); Ok(()) } /// Run `swarmctl agent agents-md apply`: send the diff in `diff_file`, made /// against `base`, and print the file's new revision. pub(crate) fn apply(target: &Target, agent: &str, base: &str, diff_file: &Path) -> Result<()> { let uri = member(agent)?; let diff = std::fs::read_to_string(diff_file) .with_context(|| format!("reading the diff {}", diff_file.display()))?; let edit = AgentsMdEdit { base_revision: base.to_owned(), diff, }; let saved: AgentsMdSaved = runtime()?.block_on(send( target, hyper::Method::PUT, &uri, Some(&edit), "agents-md-apply", ))?; println!( "applied; {agent}'s AGENTS.md is at revision {}", saved.revision ); Ok(()) }