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 => 15,
109 Self::Omp => 12,
110 Self::Claude => 14,
111 Self::Codex => 11,
112 Self::Copilot | Self::Droid | Self::Qodercli => 5,
113 Self::Devin => 4,
114 Self::Mastracode => 5,
115 Self::Kimi => 10,
116 Self::Opencode => 13,
117 Self::Kilo => 7,
118 Self::Hermes => 7,
119 Self::Qwen | Self::Cursor | Self::Grok => 3,
120 Self::AntigravityCli => 3,
121 }
122 }
123}
124
125impl fmt::Display for IntegrationTarget {
126 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127 formatter.write_str(self.cli_value())
128 }
129}
130
131impl FromStr for IntegrationTarget {
132 type Err = String;
133
134 fn from_str(value: &str) -> Result<Self, Self::Err> {
135 Self::ALL
136 .into_iter()
137 .find(|target| target.cli_value() == value)
138 .ok_or_else(|| format!("unknown integration target: {value}"))
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum LifecycleCapability {
145 Authoritative,
146 IdentityOnly,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum InstallStatus {
152 Missing,
153 Current,
154 Outdated,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
158pub struct IntegrationMetadata {
159 pub target: IntegrationTarget,
160 pub cli_value: &'static str,
161 pub label: &'static str,
162 pub lifecycle: LifecycleCapability,
163 pub available: bool,
164 pub compatible: bool,
165 pub compatibility_note: Option<&'static str>,
166 pub install_status: InstallStatus,
167 pub installed_version: Option<u32>,
168 pub expected_version: u32,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct InstallResult {
173 pub target: IntegrationTarget,
174 pub paths: Vec<std::path::PathBuf>,
175}