Skip to main content

skiagram_core/adapters/
mod.rs

1//! The `Adapter` trait — skiagram's extension point (CLAUDE.md §5).
2//!
3//! Adding a new agent = implement [`Adapter`] + register it in [`all`] + commit
4//! redacted fixtures and snapshot tests. Keep the trait minimal and stable.
5
6pub mod claude_code;
7pub mod codex;
8pub mod copilot;
9pub mod cursor;
10pub mod gemini;
11
12use crate::error::CoreError;
13use crate::model::{Session, SessionRef};
14
15/// One supported agent's on-disk format.
16pub trait Adapter {
17    /// Stable identifier used by `--agent`, e.g. `"claude-code"`.
18    fn id(&self) -> &'static str;
19    /// Cheap check: are this agent's data files present on this machine?
20    fn detect(&self) -> bool;
21    /// Find session files (read-only; never modifies anything).
22    fn discover(&self) -> anyhow::Result<Vec<SessionRef>>;
23    /// Parse one session file into the normalized model. Must be lenient:
24    /// unknown/corrupt lines are skipped with a `tracing::debug`, never a panic,
25    /// and never abort the whole file (CLAUDE.md §9).
26    fn parse(&self, r: &SessionRef) -> anyhow::Result<Session>;
27}
28
29/// Every known adapter, in priority order (fully implemented ones first; the
30/// deferred Cursor stub is last so a real coding-agent wins auto-detect).
31pub fn all() -> Vec<Box<dyn Adapter>> {
32    vec![
33        Box::new(claude_code::ClaudeCode),
34        Box::new(codex::Codex),
35        Box::new(gemini::Gemini),
36        Box::new(copilot::Copilot),
37        Box::new(cursor::Cursor),
38    ]
39}
40
41/// Look an adapter up by id (the `--agent` flag).
42pub fn by_id(id: &str) -> Result<Box<dyn Adapter>, CoreError> {
43    all()
44        .into_iter()
45        .find(|a| a.id() == id)
46        .ok_or_else(|| CoreError::UnknownAgent {
47            requested: id.to_string(),
48            known: all().iter().map(|a| a.id()).collect::<Vec<_>>().join(", "),
49        })
50}
51
52/// First adapter whose data files exist on this machine (used when `--agent` is
53/// not given).
54pub fn auto_detect() -> Option<Box<dyn Adapter>> {
55    all().into_iter().find(|a| a.detect())
56}