albatross-cli 2.0.3

Open source, terminal-first AI coding agent with fully transparent multi-model routing. Local (Ollama, LM Studio, MLX, llama.cpp) or cloud, your keys, one TUI.
use serde_json::{json, Value};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HookEventName {
    SessionStart,
    UserPromptSubmit,
    PreToolUse,
    PermissionRequest,
    PostToolUse,
    PreCompact,
    PostCompact,
    PlanUpdated,
    SubagentStart,
    SubagentStop,
    Stop,
    SessionEnd,
}

impl HookEventName {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::SessionStart => "SessionStart",
            Self::UserPromptSubmit => "UserPromptSubmit",
            Self::PreToolUse => "PreToolUse",
            Self::PermissionRequest => "PermissionRequest",
            Self::PostToolUse => "PostToolUse",
            Self::PreCompact => "PreCompact",
            Self::PostCompact => "PostCompact",
            Self::PlanUpdated => "PlanUpdated",
            Self::SubagentStart => "SubagentStart",
            Self::SubagentStop => "SubagentStop",
            Self::Stop => "Stop",
            Self::SessionEnd => "SessionEnd",
        }
    }

    pub fn key_label(self) -> &'static str {
        match self {
            Self::SessionStart => "session_start",
            Self::UserPromptSubmit => "user_prompt_submit",
            Self::PreToolUse => "pre_tool_use",
            Self::PermissionRequest => "permission_request",
            Self::PostToolUse => "post_tool_use",
            Self::PreCompact => "pre_compact",
            Self::PostCompact => "post_compact",
            Self::PlanUpdated => "plan_updated",
            Self::SubagentStart => "subagent_start",
            Self::SubagentStop => "subagent_stop",
            Self::Stop => "stop",
            Self::SessionEnd => "session_end",
        }
    }

    pub fn supports_matcher(self) -> bool {
        matches!(
            self,
            Self::SessionStart
                | Self::PreToolUse
                | Self::PermissionRequest
                | Self::PostToolUse
                | Self::PreCompact
                | Self::PostCompact
                | Self::SubagentStart
                | Self::SubagentStop
        )
    }
}

#[derive(Debug, Clone)]
pub struct HookPayload {
    value: serde_json::Map<String, Value>,
}

impl HookPayload {
    pub fn new(event: HookEventName, session_id: impl Into<String>) -> Self {
        let mut value = serde_json::Map::new();
        value.insert("hook_event_name".into(), json!(event.as_str()));
        value.insert("session_id".into(), json!(session_id.into()));
        Self { value }
    }

    pub fn turn_id(mut self, turn_id: u32) -> Self {
        self.value.insert("turn_id".into(), json!(turn_id));
        self
    }

    pub fn cwd(mut self, cwd: impl Into<String>) -> Self {
        self.value.insert("cwd".into(), json!(cwd.into()));
        self
    }

    pub fn transcript_path(mut self, path: impl Into<String>) -> Self {
        self.value
            .insert("transcript_path".into(), json!(path.into()));
        self
    }

    pub fn insert(mut self, key: impl Into<String>, value: Value) -> Self {
        self.value.insert(key.into(), value);
        self
    }

    pub fn into_value(self) -> Value {
        Value::Object(self.value)
    }
}

#[derive(Debug, Clone)]
pub struct HookInvocationContext {
    pub session_id: String,
    pub turn_id: u32,
    pub cwd: String,
    pub workspace_root: String,
    pub transcript_path: String,
    pub events_path: String,
    pub backend: String,
    pub model: String,
    pub approval_policy: String,
    pub source: String,
}

impl HookInvocationContext {
    pub fn payload(&self, event: HookEventName) -> HookPayload {
        HookPayload::new(event, self.session_id.clone())
            .turn_id(self.turn_id)
            .cwd(self.cwd.clone())
            .transcript_path(self.transcript_path.clone())
            .insert("workspace_root", json!(self.workspace_root))
            .insert("events_path", json!(self.events_path))
            .insert("backend", json!(self.backend))
            .insert("model", json!(self.model))
            .insert("approval_policy", json!(self.approval_policy))
            .insert("source", json!(self.source))
    }
}