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}