Skip to main content

wsx_core/integration/
model.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
6#[serde(rename_all = "kebab-case")]
7pub enum IntegrationTarget {
8    Pi,
9    Omp,
10    Claude,
11    Codex,
12    Copilot,
13    Devin,
14    Droid,
15    Kimi,
16    Opencode,
17    Kilo,
18    Hermes,
19    Qodercli,
20    Qwen,
21    Cursor,
22    Mastracode,
23    AntigravityCli,
24    Grok,
25}
26
27impl IntegrationTarget {
28    pub const ALL: [Self; 17] = [
29        Self::Pi,
30        Self::Omp,
31        Self::Claude,
32        Self::Codex,
33        Self::Copilot,
34        Self::Devin,
35        Self::Droid,
36        Self::Kimi,
37        Self::Opencode,
38        Self::Kilo,
39        Self::Hermes,
40        Self::Qodercli,
41        Self::Qwen,
42        Self::Cursor,
43        Self::Mastracode,
44        Self::AntigravityCli,
45        Self::Grok,
46    ];
47
48    pub const fn cli_value(self) -> &'static str {
49        match self {
50            Self::Pi => "pi",
51            Self::Omp => "omp",
52            Self::Claude => "claude",
53            Self::Codex => "codex",
54            Self::Copilot => "copilot",
55            Self::Devin => "devin",
56            Self::Droid => "droid",
57            Self::Kimi => "kimi",
58            Self::Opencode => "opencode",
59            Self::Kilo => "kilo",
60            Self::Hermes => "hermes",
61            Self::Qodercli => "qodercli",
62            Self::Qwen => "qwen",
63            Self::Cursor => "cursor",
64            Self::Mastracode => "mastracode",
65            Self::AntigravityCli => "antigravity-cli",
66            Self::Grok => "grok",
67        }
68    }
69
70    pub const fn label(self) -> &'static str {
71        match self {
72            Self::Pi => "Pi",
73            Self::Omp => "OMP",
74            Self::Claude => "Claude Code",
75            Self::Codex => "Codex",
76            Self::Copilot => "GitHub Copilot CLI",
77            Self::Devin => "Devin CLI",
78            Self::Droid => "Factory Droid",
79            Self::Kimi => "Kimi Code",
80            Self::Opencode => "OpenCode",
81            Self::Kilo => "Kilo Code",
82            Self::Hermes => "Hermes Agent",
83            Self::Qodercli => "Qoder CLI",
84            Self::Qwen => "Qwen Code",
85            Self::Cursor => "Cursor Agent",
86            Self::Mastracode => "MastraCode",
87            Self::AntigravityCli => "Antigravity CLI",
88            Self::Grok => "Grok CLI",
89        }
90    }
91
92    pub const fn lifecycle(self) -> LifecycleCapability {
93        match self {
94            Self::Pi
95            | Self::Omp
96            | Self::Claude
97            | Self::Codex
98            | Self::Kimi
99            | Self::Opencode
100            | Self::Kilo
101            | Self::Mastracode => LifecycleCapability::Authoritative,
102            _ => LifecycleCapability::IdentityOnly,
103        }
104    }
105
106    pub const fn expected_version(self) -> u32 {
107        match self {
108            Self::Pi => 14,
109            Self::Omp => 11,
110            Self::Claude => 12,
111            Self::Codex => 10,
112            Self::Copilot | Self::Droid | Self::Qodercli => 4,
113            Self::Devin | Self::AntigravityCli => 3,
114            Self::Mastracode => 4,
115            Self::Kimi => 9,
116            Self::Opencode => 13,
117            Self::Kilo => 7,
118            Self::Hermes => 6,
119            Self::Qwen | Self::Cursor | Self::Grok => 2,
120        }
121    }
122}
123
124impl fmt::Display for IntegrationTarget {
125    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126        formatter.write_str(self.cli_value())
127    }
128}
129
130impl FromStr for IntegrationTarget {
131    type Err = String;
132
133    fn from_str(value: &str) -> Result<Self, Self::Err> {
134        Self::ALL
135            .into_iter()
136            .find(|target| target.cli_value() == value)
137            .ok_or_else(|| format!("unknown integration target: {value}"))
138    }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "snake_case")]
143pub enum LifecycleCapability {
144    Authoritative,
145    IdentityOnly,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(rename_all = "snake_case")]
150pub enum InstallStatus {
151    Missing,
152    Current,
153    Outdated,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
157pub struct IntegrationMetadata {
158    pub target: IntegrationTarget,
159    pub cli_value: &'static str,
160    pub label: &'static str,
161    pub lifecycle: LifecycleCapability,
162    pub available: bool,
163    pub compatible: bool,
164    pub compatibility_note: Option<&'static str>,
165    pub install_status: InstallStatus,
166    pub installed_version: Option<u32>,
167    pub expected_version: u32,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct InstallResult {
172    pub target: IntegrationTarget,
173    pub paths: Vec<std::path::PathBuf>,
174}