sessionwiki 0.34.9

Find, search, and read every AI coding session you've ever had - across Claude Code, Codex, Gemini CLI, OpenCode, Cline, and more.
Documentation
use chrono::{DateTime, Utc};
use serde::Serialize;
use serde_json::Value;
use std::path::PathBuf;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    User,
    Assistant,
    Tool,
}

impl Role {
    pub fn label(&self) -> &'static str {
        match self {
            Role::User => "user",
            Role::Assistant => "assistant",
            Role::Tool => "tool",
        }
    }
}

impl FromStr for Role {
    type Err = &'static str;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "user" => Ok(Role::User),
            "assistant" => Ok(Role::Assistant),
            "tool" => Ok(Role::Tool),
            _ => Err("role must be user, assistant, or tool"),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Message {
    pub role: Role,
    pub text: String,
    pub ts: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool: Option<ToolEvent>,
}

/// Structured tool data before or after the shared summary pass.
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum ToolEvent {
    Part(ToolPart),
    Summary(ToolSummary),
}

/// A tool call or result emitted by an adapter before pairing.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolPart {
    Call(ToolCall),
    Result(ToolResult),
}

#[derive(Debug, Clone, Serialize)]
pub struct ToolCall {
    pub id: Option<String>,
    pub name: String,
    pub args: Value,
}

#[derive(Debug, Clone, Serialize)]
pub struct ToolResult {
    pub call_id: Option<String>,
    pub text: String,
    pub is_error: bool,
}

/// Paired tool data retained in memory after its compact line is rendered.
#[derive(Debug, Clone, Serialize)]
pub struct ToolSummary {
    pub name: String,
    pub args: Value,
    pub output: Option<String>,
    pub is_error: Option<bool>,
    pub lines: Option<usize>,
}
impl Message {
    pub fn tool_call(ts: Option<DateTime<Utc>>, call: ToolCall) -> Self {
        Self {
            role: Role::Tool,
            text: String::new(),
            ts,
            tool: Some(ToolEvent::Part(ToolPart::Call(call))),
        }
    }

    pub fn tool_result(ts: Option<DateTime<Utc>>, result: ToolResult) -> Self {
        Self {
            role: Role::Tool,
            text: String::new(),
            ts,
            tool: Some(ToolEvent::Part(ToolPart::Result(result))),
        }
    }
}

/// The write tool behind one file edit, normalized across the variants a tool
/// exposes - the evidence of *what kind* of change a session made to a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum EditKind {
    Edit,
    Write,
    MultiEdit,
    NotebookEdit,
}

impl EditKind {
    /// Stable lowercase token stored in the index (matches the Serialize form).
    pub fn as_str(&self) -> &'static str {
        match self {
            EditKind::Edit => "edit",
            EditKind::Write => "write",
            EditKind::MultiEdit => "multiedit",
            EditKind::NotebookEdit => "notebookedit",
        }
    }
}

/// One concrete edit a session made to one file - the evidence chain's atom.
/// `snippet` is a bounded excerpt of the change (the resulting code, or the
/// created content) so a later reader can see WHAT changed without re-opening
/// the original session.
#[derive(Debug, Serialize)]
pub struct EditEvent {
    pub path: String,
    pub kind: EditKind,
    pub snippet: String,
    pub ts: Option<DateTime<Utc>>,
}

#[derive(Debug, Serialize)]
pub struct Session {
    /// Short stable id derived from the file path (FNV-1a hash, hex).
    pub id: String,
    pub tool: &'static str,
    pub path: PathBuf,
    pub project: String,
    pub started: Option<DateTime<Utc>>,
    pub ended: Option<DateTime<Utc>>,
    pub title: String,
    /// True for subagent transcripts spawned inside a parent session.
    pub subagent: bool,
    pub messages: Vec<Message>,
    /// Files the session edited or created, extracted from its tool calls
    /// (Claude's Edit/Write, Codex's apply_patch). This is the link between a
    /// session and the code it produced - the basis for `files` and `blame`.
    pub touched: Vec<String>,
    /// The concrete edits behind `touched`, per file - the evidence layer: what
    /// kind of change and a bounded snippet of it. Empty for adapters that do
    /// not yet extract structured edits (they still populate `touched`).
    pub edits: Vec<EditEvent>,
}

/// One discovered session store on disk (for `scan`).
pub struct StoreReport {
    pub tool: &'static str,
    pub root: PathBuf,
    pub files: usize,
    pub bytes: u64,
    pub oldest: Option<DateTime<Utc>>,
    pub newest: Option<DateTime<Utc>>,
}