sloop-daemon 0.5.0

Agentic coding scheduler — a daemon that runs background coding agents autonomously in isolated git worktrees
Documentation
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use crate::flow::Flow;

use super::{FlowSource, SourceError};

pub struct MarkdownFlowSource {
    root: PathBuf,
}

impl MarkdownFlowSource {
    pub fn new(root: impl Into<PathBuf>) -> Self {
        Self { root: root.into() }
    }
}

impl FlowSource for MarkdownFlowSource {
    fn pull(&self) -> Result<Vec<Flow>, SourceError> {
        let directory = self.root.join(".agents/sloop/flows");
        let entries = match fs::read_dir(&directory) {
            Ok(entries) => entries,
            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
            Err(error) => return Err(io_error(&directory, error)),
        };
        let mut paths = entries
            .map(|entry| {
                entry
                    .map(|entry| entry.path())
                    .map_err(|error| io_error(&directory, error))
            })
            .collect::<Result<Vec<_>, _>>()?;
        paths.retain(|path| path.is_file() && path.extension().is_some_and(|ext| ext == "yaml"));
        paths.sort();

        paths
            .into_iter()
            .map(|path| {
                let name = path
                    .file_stem()
                    .and_then(|stem| stem.to_str())
                    .ok_or_else(|| {
                        SourceError::new(format!(
                            "{}: flow filename must be valid UTF-8",
                            path.display()
                        ))
                    })?;
                let contents = fs::read_to_string(&path).map_err(|error| io_error(&path, error))?;
                crate::flow::parse(name, &contents)
                    .map_err(|message| SourceError::new(format!("{}: {message}", path.display())))
            })
            .collect()
    }
}

fn io_error(path: &Path, error: io::Error) -> SourceError {
    SourceError::new(format!("{}: {error}", path.display()))
}