//! Importer trait and config-shape types consumed by the UI (dynamic form //! rendering) and the CLI (future). Scripts can serialise ImportConfig as JSON. use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use thiserror::Error; use uuid::Uuid; use crate::coordinator::Archivist; use super::progress::ImportProgressSink; #[async_trait] pub trait Importer: Send + Sync { fn source_name(&self) -> &'static str; fn config_shape(&self) -> ImportConfigShape; async fn discover( &self, cfg: &ImportConfig, ) -> Result; async fn import( &self, cfg: &ImportConfig, archivist: &Archivist, target: ImportTarget, progress: ImportProgressSink, ) -> Result; /// Attempt to auto-detect default configuration values. /// /// Importers that can discover their source location automatically /// (e.g., Claude Code's `~/.claude` directory) should override this. /// Returns `None` when auto-detection is not supported or fails. fn detect_defaults(&self) -> Option { None } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImporterInfo { pub source_name: String, pub display_name: String, pub config_shape: ImportConfigShape, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImportConfigShape { pub fields: Vec, pub example: ImportConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigField { pub key: String, pub label: String, pub kind: ConfigFieldKind, pub required: bool, pub help: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "type")] pub enum ConfigFieldKind { Path { directory: bool }, File { extension: Option }, String, Bool, Enum { variants: Vec }, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ImportConfig { pub source: String, #[serde(default)] pub params: BTreeMap, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ImportTarget { pub archive: Option, pub connector_alias: Option, pub project_id: Option, /// Maps normalized project_path -> project_id (as string UUID). /// When a session's project_path is found in this map, the corresponding /// project_id is injected into the session metadata during import. #[serde(default)] pub project_map: HashMap, } #[derive(Debug, Error)] pub enum ImportError { #[error("source not found: {0}")] SourceNotFound(String), #[error("config: {0}")] Config(String), #[error("discovery: {0}")] Discovery(String), #[error("I/O: {0}")] Io(#[from] std::io::Error), #[error("archivist: {0}")] Archivist(String), #[error("parser: {0}")] Parser(String), #[error("cancelled")] Cancelled, } #[cfg(test)] mod tests { use super::*; #[test] fn config_round_trips() { let cfg = ImportConfig { source: "claude".into(), params: BTreeMap::new() }; let json = serde_json::to_string(&cfg).unwrap(); let back: ImportConfig = serde_json::from_str(&json).unwrap(); assert_eq!(back.source, "claude"); } }