use std::process::Command;
use serde::Deserialize;
use anyhow::Result;
#[derive(Debug, Clone, Deserialize)]
pub struct FleetRoom {
pub repo: String,
#[serde(rename = "type")]
pub room_type: String,
pub agents: Vec<String>,
pub constraints: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FleetIndex {
pub rooms: Vec<FleetRoom>,
}
#[derive(Debug, Clone)]
pub struct Repo {
pub name: String,
pub path: String,
pub remote: String,
}
pub struct GitRuntime {
workspace: String,
agora_path: Option<String>,
fleet_index: Option<FleetIndex>,
}
impl GitRuntime {
pub async fn new() -> Result<Self> {
Ok(Self {
workspace: "/tmp/plato-workspace".to_string(),
agora_path: None,
fleet_index: None,
})
}
pub async fn join_fleet(&mut self, agora_remote: &str) -> Result<()> {
let agora_path = format!("{}/agora", self.workspace);
let exists = std::path::Path::new(&agora_path).exists();
if exists {
let output = Command::new("git")
.args(["-C", &agora_path, "pull", "origin", "main"])
.output()?;
tracing::info!("Pulled Agora: {}", String::from_utf8_lossy(&output.stderr));
} else {
let output = Command::new("git")
.args(["clone", agora_remote, &agora_path])
.output()?;
tracing::info!("Cloned Agora: {}", String::from_utf8_lossy(&output.stderr));
}
let index_path = format!("{}/plato-index.yaml", agora_path);
if std::path::Path::new(&index_path).exists() {
let index_content = std::fs::read_to_string(&index_path)?;
let fleet_index: FleetIndex = serde_yaml::from_str(&index_content)?;
self.fleet_index = Some(fleet_index);
tracing::info!("Loaded fleet index with {} rooms", self.fleet_index.as_ref().map_or(0, |f| f.rooms.len()));
} else {
tracing::warn!("Agora missing plato-index.yaml, fleet discovery disabled");
}
self.agora_path = Some(agora_path);
Ok(())
}
pub async fn list_fleet_rooms(&self) -> Result<&[FleetRoom]> {
match &self.fleet_index {
Some(index) => Ok(&index.rooms),
None => Err(anyhow::anyhow!("Not joined to a fleet, call join_fleet first"))
}
}
pub async fn checkout(&self, room: &str) -> Result<Repo> {
let remote = if let Some(index) = &self.fleet_index {
index.rooms.iter()
.find(|r| r.repo.ends_with(&format!("/{}", room)) || r.repo == room)
.map(|r| format!("https://{}.git", r.repo))
.unwrap_or_else(|| format!("https://github.com/SuperInstance/{}.git", room))
} else {
format!("https://github.com/SuperInstance/{}.git", room)
};
let repo_path = format!("{}/{}", self.workspace, room);
let exists = std::path::Path::new(&repo_path).exists();
if exists {
let output = Command::new("git")
.args(["-C", &repo_path, "pull", "origin", "main"])
.output()?;
tracing::info!("Pulled {}: {}", room, String::from_utf8_lossy(&output.stderr));
} else {
let output = Command::new("git")
.args(["clone", &remote, &repo_path])
.output()?;
tracing::info!("Cloned {} from {}: {}", room, remote, String::from_utf8_lossy(&output.stderr));
}
Ok(Repo {
name: room.to_string(),
path: repo_path,
remote,
})
}
pub async fn get_constraints(&self, repo: &Repo) -> Result<String> {
let constraints_path = format!("{}/.plato/CONSTRAINTS.yaml", repo.path);
if std::path::Path::new(&constraints_path).exists() {
Ok(std::fs::read_to_string(&constraints_path)?)
} else {
Ok("constraints: []".to_string())
}
}
pub async fn get_room_description(&self, repo: &Repo) -> Result<String> {
let room_path = format!("{}/ROOM.md", repo.path);
if std::path::Path::new(&room_path).exists() {
Ok(std::fs::read_to_string(&room_path)?)
} else {
Ok(format!("# {}\n\nA room in the Plato MUD.", repo.name))
}
}
}