use std::sync::Arc;
use arc_swap::ArcSwap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub enum PermissionMode {
#[default]
Default,
AcceptEdits,
Plan,
Auto,
DontAsk,
BypassPermissions,
}
impl PermissionMode {
#[must_use]
pub fn next(self) -> Self {
match self {
Self::Default => Self::AcceptEdits,
Self::AcceptEdits => Self::Plan,
Self::Plan => Self::Auto,
Self::Auto => Self::DontAsk,
Self::DontAsk => Self::BypassPermissions,
Self::BypassPermissions => Self::Default,
}
}
#[must_use]
pub fn prev(self) -> Self {
match self {
Self::Default => Self::BypassPermissions,
Self::AcceptEdits => Self::Default,
Self::Plan => Self::AcceptEdits,
Self::Auto => Self::Plan,
Self::DontAsk => Self::Auto,
Self::BypassPermissions => Self::DontAsk,
}
}
#[must_use]
pub fn chip(self) -> &'static str {
match self {
Self::Default => "",
Self::AcceptEdits => "\u{270e} accept edits",
Self::Plan => "\u{1f4cb} plan",
Self::Auto => "\u{1f916} auto",
Self::DontAsk => "\u{23ed} don't ask",
Self::BypassPermissions => "\u{26a0} bypass",
}
}
pub fn parse(s: &str) -> Result<Self, String> {
match s {
"default" => Ok(Self::Default),
"acceptEdits" => Ok(Self::AcceptEdits),
"plan" => Ok(Self::Plan),
"auto" => Ok(Self::Auto),
"dontAsk" => Ok(Self::DontAsk),
"bypassPermissions" => Ok(Self::BypassPermissions),
other => Err(other.into()),
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Default => "default",
Self::AcceptEdits => "acceptEdits",
Self::Plan => "plan",
Self::Auto => "auto",
Self::DontAsk => "dontAsk",
Self::BypassPermissions => "bypassPermissions",
}
}
}
impl std::str::FromStr for PermissionMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl std::fmt::Display for PermissionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct SharedPermissionMode {
inner: Arc<ArcSwap<PermissionMode>>,
}
impl SharedPermissionMode {
#[must_use]
pub fn new(mode: PermissionMode) -> Self {
Self {
inner: Arc::new(ArcSwap::from_pointee(mode)),
}
}
#[must_use]
pub fn load(&self) -> PermissionMode {
**self.inner.load()
}
pub fn store(&self, mode: PermissionMode) {
self.inner.store(Arc::new(mode));
}
}
impl Default for SharedPermissionMode {
fn default() -> Self {
Self::new(PermissionMode::Default)
}
}
pub fn resolve_startup_mode(
cli: Option<&str>,
env_var: Option<&str>,
settings_default_mode: Option<&str>,
bypass_latch: bool,
) -> Result<PermissionMode, String> {
let mode = if let Some(s) = cli {
PermissionMode::parse(s)
.map_err(|bad| format!("--permission-mode: unknown mode '{bad}'"))?
} else if let Some(s) = env_var {
PermissionMode::parse(s)
.map_err(|bad| format!("CALIBAN_DEFAULT_PERMISSION_MODE: unknown mode '{bad}'"))?
} else if let Some(s) = settings_default_mode {
PermissionMode::parse(s)
.map_err(|bad| format!("permissions.default_mode: unknown mode '{bad}'"))?
} else {
PermissionMode::Default
};
if mode == PermissionMode::BypassPermissions && !bypass_latch {
return Err("bypassPermissions requires --allow-dangerously-skip-permissions".into());
}
Ok(mode)
}
pub const FILE_EDIT_TOOLS: &[&str] = &["Write", "Edit", "MultiEdit", "NotebookEdit"];
#[must_use]
pub fn is_file_edit_tool(tool_name: &str) -> bool {
FILE_EDIT_TOOLS.contains(&tool_name)
}