#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum HostAdapter {
Claude,
Codex,
Hermes,
OpenClaw,
}
impl HostAdapter {
pub const ALL: &'static [Self] = &[Self::Claude, Self::Codex, Self::Hermes, Self::OpenClaw];
pub fn as_str(self) -> &'static str {
match self {
Self::Claude => "claude",
Self::Codex => "codex",
Self::Hermes => "hermes",
Self::OpenClaw => "openclaw",
}
}
pub fn from_id(name: &str) -> Option<Self> {
match name {
"claude" | "claude-code" => Some(Self::Claude),
"codex" => Some(Self::Codex),
"hermes" => Some(Self::Hermes),
"openclaw" => Some(Self::OpenClaw),
_ => None,
}
}
pub fn default_mode(self) -> IntegrationMode {
match self {
Self::Claude => IntegrationMode::NativeHook,
Self::Codex => IntegrationMode::ManualSkill,
Self::Hermes | Self::OpenClaw => IntegrationMode::ReferenceAdapter,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum IntegrationMode {
ManualSkill,
LauncherWrapper,
NativeHook,
ReferenceAdapter,
}
impl IntegrationMode {
pub const ALL: &'static [Self] = &[
Self::ManualSkill,
Self::LauncherWrapper,
Self::NativeHook,
Self::ReferenceAdapter,
];
pub fn as_str(self) -> &'static str {
match self {
Self::ManualSkill => "manual_skill",
Self::LauncherWrapper => "launcher_wrapper",
Self::NativeHook => "native_hook",
Self::ReferenceAdapter => "reference_adapter",
}
}
pub fn from_id(value: &str) -> Option<Self> {
match value {
"manual_skill" => Some(Self::ManualSkill),
"launcher_wrapper" => Some(Self::LauncherWrapper),
"native_hook" => Some(Self::NativeHook),
"reference_adapter" => Some(Self::ReferenceAdapter),
_ => None,
}
}
pub fn from_lifecycle_mode(mode: crate::IntegrationMode) -> Option<Self> {
match mode {
crate::IntegrationMode::ManualSkill => Some(Self::ManualSkill),
crate::IntegrationMode::LauncherWrapper => Some(Self::LauncherWrapper),
crate::IntegrationMode::NativeHook => Some(Self::NativeHook),
crate::IntegrationMode::ReferenceAdapter => Some(Self::ReferenceAdapter),
crate::IntegrationMode::TelemetryOnly => None,
}
}
}
pub fn supports_mode(host: HostAdapter, mode: IntegrationMode) -> bool {
match host {
HostAdapter::Claude => mode == IntegrationMode::NativeHook,
HostAdapter::Codex => matches!(
mode,
IntegrationMode::ManualSkill
| IntegrationMode::LauncherWrapper
| IntegrationMode::NativeHook
),
HostAdapter::Hermes | HostAdapter::OpenClaw => mode == IntegrationMode::ReferenceAdapter,
}
}
pub fn supported_modes(host: HostAdapter) -> &'static [IntegrationMode] {
match host {
HostAdapter::Claude => &[IntegrationMode::NativeHook],
HostAdapter::Codex => &[
IntegrationMode::ManualSkill,
IntegrationMode::LauncherWrapper,
IntegrationMode::NativeHook,
],
HostAdapter::Hermes | HostAdapter::OpenClaw => &[IntegrationMode::ReferenceAdapter],
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RenderedAsset {
pub relative_path: &'static str,
pub contents: String,
pub mode: Option<u32>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AssetStatus {
Present,
Missing,
Drifted,
InvalidMode,
NotApplicable,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FileAction {
Installed,
Updated,
AlreadyPresent,
}
pub fn combine_actions(current: FileAction, next: FileAction) -> FileAction {
match (current, next) {
(FileAction::Updated, _) | (_, FileAction::Updated) => FileAction::Updated,
(FileAction::Installed, _) | (_, FileAction::Installed) => FileAction::Installed,
_ => FileAction::AlreadyPresent,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MergedFile {
pub existing: Option<String>,
pub rendered: String,
}
#[derive(Debug)]
pub enum HostAssetError {
UnsupportedMode {
host: HostAdapter,
mode: IntegrationMode,
},
Malformed { reason: String },
Parse { reason: String },
Serialize { reason: String },
}
impl std::fmt::Display for HostAssetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedMode { host, mode } => write!(
f,
"unsupported mode `{}` for {} (supported: {})",
mode.as_str(),
host.as_str(),
supported_modes(*host)
.iter()
.map(|m| m.as_str())
.collect::<Vec<_>>()
.join(", ")
),
Self::Malformed { reason } => write!(f, "malformed managed file: {reason}"),
Self::Parse { reason } => write!(f, "parse error: {reason}"),
Self::Serialize { reason } => write!(f, "serialize error: {reason}"),
}
}
}
impl std::error::Error for HostAssetError {}
pub const CLAUDE_SOURCE_SETTINGS: &str = ".ccd-hosts/claude/settings.json";
pub const CLAUDE_TARGET_SETTINGS: &str = ".claude/settings.json";
pub const CODEX_SOURCE_README: &str = ".ccd-hosts/codex/README.md";
pub const CODEX_SOURCE_LAUNCHER: &str = ".ccd-hosts/codex/launcher.sh";
pub const CODEX_SOURCE_CONFIG: &str = ".ccd-hosts/codex/config.toml";
pub const CODEX_SOURCE_HOOKS: &str = ".ccd-hosts/codex/hooks.json";
pub const CODEX_TARGET_LAUNCHER: &str = ".codex/ccd-launch.sh";
pub const CODEX_TARGET_CONFIG: &str = ".codex/config.toml";
pub const CODEX_TARGET_HOOKS: &str = ".codex/hooks.json";
pub const OPENCLAW_SOURCE_ADAPTER: &str = ".ccd-hosts/openclaw/adapter.json";
pub const OPENCLAW_TARGET_ADAPTER: &str = ".openclaw/ccd.json";
pub const HERMES_SOURCE_ADAPTER: &str = ".ccd-hosts/hermes/adapter.json";
pub const HERMES_TARGET_ADAPTER: &str = ".hermes/ccd.json";