use serde::{Deserialize, Serialize};
use crate::kind::Kind;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Action {
Shell {
command: String,
},
ForeignShell {
shell: String,
command: String,
},
FsRead {
path: String,
},
FsWrite {
path: String,
},
Net {
url: String,
},
Fetch {
url: String,
},
Patch {
writes: Vec<String>,
},
ReadFiles {
paths: Vec<String>,
},
McpTool {
name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
reads: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
writes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hosts: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
unchecked: Option<String>,
},
}
impl Action {
#[must_use]
pub fn kind(&self) -> Kind {
match self {
Self::Shell { .. } | Self::ForeignShell { .. } => Kind::Shell,
Self::FsRead { .. } | Self::ReadFiles { .. } => Kind::FsRead,
Self::FsWrite { .. } | Self::Patch { .. } => Kind::FsWrite,
Self::Net { .. } => Kind::Net,
Self::Fetch { .. } => Kind::Fetch,
Self::McpTool { .. } => Kind::Mcp,
}
}
#[must_use]
pub fn mcp(name: impl Into<String>) -> Self {
Self::McpTool {
name: name.into(),
reads: Vec::new(),
writes: Vec::new(),
hosts: Vec::new(),
unchecked: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AtomicAction {
Shell {
argv: Vec<String>,
},
Pipeline {
argv: Vec<String>,
},
FsRead {
path: String,
},
FsWrite {
path: String,
},
Net {
host: String,
},
Fetch {
host: String,
},
EnvRead {
name: String,
},
EnvSet {
name: String,
},
McpTool {
name: String,
},
}
impl AtomicAction {
#[must_use]
pub fn kind(&self) -> Kind {
match self {
Self::Shell { .. } | Self::Pipeline { .. } => Kind::Shell,
Self::FsRead { .. } => Kind::FsRead,
Self::FsWrite { .. } => Kind::FsWrite,
Self::Net { .. } => Kind::Net,
Self::Fetch { .. } => Kind::Fetch,
Self::EnvRead { .. } => Kind::EnvRead,
Self::EnvSet { .. } => Kind::EnvSet,
Self::McpTool { .. } => Kind::Mcp,
}
}
pub(crate) fn with_path(&self, path: String) -> Option<Self> {
match self {
Self::FsRead { .. } => Some(Self::FsRead { path }),
Self::FsWrite { .. } => Some(Self::FsWrite { path }),
_ => None,
}
}
pub(crate) fn subject(&self) -> Option<&str> {
match self {
Self::Shell { .. } | Self::Pipeline { .. } => None,
Self::FsRead { path } | Self::FsWrite { path } => Some(path),
Self::Net { host } | Self::Fetch { host } => Some(host),
Self::EnvRead { name } | Self::EnvSet { name } | Self::McpTool { name } => Some(name),
}
}
#[must_use]
pub fn describe(&self) -> String {
match self {
Self::Shell { argv } => format!("shell \"{}\"", argv.join(" ")),
Self::Pipeline { argv } => format!("pipeline \"{}\"", argv.join(" ")),
Self::FsRead { path } => format!("read {path}"),
Self::FsWrite { path } => format!("write {path}"),
Self::Net { host } => format!("net {host}"),
Self::Fetch { host } => format!("fetch {host}"),
Self::EnvRead { name } => format!("env read {name}"),
Self::EnvSet { name } => format!("env set {name}"),
Self::McpTool { name } => format!("mcp {name}"),
}
}
}