muster-workspace 0.3.0

A terminal workspace for running CLI agents and dev processes side by side
Documentation
use std::path::PathBuf;

use getset::{Getters, WithSetters};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

use crate::domain::{
    process::{AgentTool, Process, ProcessKind, RestartPolicy, StopPolicy},
    value::{CommandLine, Description, PaneId, ProcessName},
};

/// Declarative definition of one process within a workspace section.
#[derive(Clone, Debug, Serialize, Deserialize, Getters, WithSetters, TypedBuilder)]
#[set_with]
pub struct ProcessSpec {
    #[getset(get = "pub", set_with = "pub")]
    name: ProcessName,
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    command: Option<CommandLine>,
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    working_dir: Option<PathBuf>,
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    description: Option<Description>,
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    restart: Option<RestartPolicy>,
    /// Optional graceful shutdown policy. Only command specs may configure it.
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    stop: Option<StopPolicy>,
    /// Whether to launch this process automatically on load. Absent means the
    /// per-kind default; `true`/`false` overrides it.
    #[getset(get = "pub", set_with = "pub")]
    #[builder(default)]
    autostart: Option<bool>,
}

impl ProcessSpec {
    /// Builds the corresponding `Process` entity in its initial `Pending` state.
    pub fn to_process(&self, id: PaneId, kind: ProcessKind) -> Process {
        Process::builder()
            .id(id)
            .name(self.name.clone())
            .kind(kind)
            .agent_tool(
                (kind == ProcessKind::Agent)
                    .then(|| AgentTool::from_command(self.command.as_ref())),
            )
            .command(self.command.clone())
            .working_dir(self.working_dir.clone())
            .description(self.description.clone())
            .restart(self.restart_policy())
            .stop(if kind == ProcessKind::Command {
                self.stop.clone()
            } else {
                None
            })
            .autostart(self.should_autostart(kind))
            .build()
    }

    /// The effective restart policy, treating an absent policy as `Never`.
    pub fn restart_policy(&self) -> RestartPolicy {
        self.restart.unwrap_or(RestartPolicy::Never)
    }

    /// Whether this process auto-starts. Defaults to false for commands (which
    /// wait for an explicit start) and true for agents and terminals.
    pub fn should_autostart(&self, kind: ProcessKind) -> bool {
        self.autostart.unwrap_or(kind != ProcessKind::Command)
    }

    /// The effective graceful shutdown policy for `kind`. Commands use the
    /// domain default when the config omits `stop`; other kinds have no policy.
    pub fn effective_stop_policy(&self, kind: ProcessKind) -> Option<StopPolicy> {
        if kind == ProcessKind::Command {
            Some(self.stop.clone().unwrap_or_default())
        } else {
            None
        }
    }
}