1use crate::artifact::ArtifactType;
7
8#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, clap::ValueEnum)]
16#[value(rename_all = "lower")]
17pub enum Harness {
18 Claude,
19 Gemini,
20 Codex,
21 Opencode,
22 Cursor,
23 Pi,
24 Copilot,
25}
26
27impl Harness {
28 pub(crate) const ALL: [Harness; 7] = [
30 Harness::Claude,
31 Harness::Gemini,
32 Harness::Codex,
33 Harness::Opencode,
34 Harness::Cursor,
35 Harness::Pi,
36 Harness::Copilot,
37 ];
38
39 pub(crate) fn artifact_type(&self) -> ArtifactType {
41 match self {
42 Harness::Claude => ArtifactType::Claude,
43 Harness::Gemini => ArtifactType::Gemini,
44 Harness::Codex => ArtifactType::Codex,
45 Harness::Opencode => ArtifactType::Opencode,
46 Harness::Cursor => ArtifactType::Cursor,
47 Harness::Pi => ArtifactType::Pi,
48 Harness::Copilot => ArtifactType::Copilot,
49 }
50 }
51
52 pub(crate) fn name(&self) -> &'static str {
53 self.artifact_type().name()
54 }
55
56 pub(crate) fn padded_name(&self) -> String {
57 self.artifact_type().padded_name()
58 }
59}
60
61impl ArtifactType {
62 pub(crate) fn harness(&self) -> Option<Harness> {
65 match self {
66 ArtifactType::Claude => Some(Harness::Claude),
67 ArtifactType::Gemini => Some(Harness::Gemini),
68 ArtifactType::Codex => Some(Harness::Codex),
69 ArtifactType::Opencode => Some(Harness::Opencode),
70 ArtifactType::Cursor => Some(Harness::Cursor),
71 ArtifactType::Pi => Some(Harness::Pi),
72 ArtifactType::Copilot => Some(Harness::Copilot),
73 ArtifactType::Git => None,
74 }
75 }
76}
77
78#[derive(Default)]
82pub(crate) struct HarnessBundle {
83 pub(crate) claude: Option<toolpath_claude::ClaudeConvo>,
84 pub(crate) gemini: Option<toolpath_gemini::GeminiConvo>,
85 pub(crate) codex: Option<toolpath_codex::CodexConvo>,
86 pub(crate) copilot: Option<toolpath_copilot::CopilotConvo>,
87 pub(crate) opencode: Option<toolpath_opencode::OpencodeConvo>,
88 pub(crate) cursor: Option<toolpath_cursor::CursorConvo>,
89 pub(crate) pi: Option<toolpath_pi::PiConvo>,
90}
91
92impl HarnessBundle {
93 pub(crate) fn from_environment() -> Self {
97 Self {
98 claude: Some(toolpath_claude::ClaudeConvo::new()),
99 gemini: Some(toolpath_gemini::GeminiConvo::new()),
100 codex: Some(toolpath_codex::CodexConvo::new()),
101 copilot: Some(toolpath_copilot::CopilotConvo::new()),
102 opencode: Some(toolpath_opencode::OpencodeConvo::new()),
103 cursor: Some(toolpath_cursor::CursorConvo::new()),
104 pi: Some(toolpath_pi::PiConvo::new()),
105 }
106 }
107}
108
109pub(crate) fn is_not_found_claude(err: &toolpath_claude::ConvoError) -> bool {
110 use toolpath_claude::ConvoError;
111 matches!(err, ConvoError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
112 || matches!(err, ConvoError::NoHomeDirectory)
113 || matches!(err, ConvoError::ClaudeDirectoryNotFound(_))
114}
115
116pub(crate) fn is_not_found_gemini(err: &toolpath_gemini::ConvoError) -> bool {
117 use toolpath_gemini::ConvoError;
118 matches!(err, ConvoError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
119 || matches!(err, ConvoError::NoHomeDirectory)
120 || matches!(err, ConvoError::GeminiDirectoryNotFound(_))
121}
122
123pub(crate) fn is_not_found_pi(err: &toolpath_pi::PiError) -> bool {
124 use toolpath_pi::PiError;
125 matches!(err, PiError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
126 || matches!(err, PiError::ProjectNotFound(_))
127}
128
129pub(crate) fn is_not_found_codex(err: &toolpath_codex::ConvoError) -> bool {
130 use toolpath_codex::ConvoError;
131 matches!(err, ConvoError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
132 || matches!(err, ConvoError::NoHomeDirectory)
133 || matches!(err, ConvoError::CodexDirectoryNotFound(_))
134}
135
136pub(crate) fn is_not_found_copilot(err: &toolpath_copilot::ConvoError) -> bool {
137 use toolpath_copilot::ConvoError;
138 matches!(err, ConvoError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
139 || matches!(err, ConvoError::NoHomeDirectory)
140 || matches!(err, ConvoError::CopilotDirectoryNotFound(_))
141}
142
143pub(crate) fn is_not_found_opencode(err: &toolpath_opencode::ConvoError) -> bool {
144 use toolpath_opencode::ConvoError;
145 matches!(err, ConvoError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
146 || matches!(err, ConvoError::NoHomeDirectory)
147 || matches!(err, ConvoError::OpencodeDirectoryNotFound(_))
148 || matches!(err, ConvoError::DatabaseNotFound(_))
149}
150
151pub(crate) fn is_not_found_cursor(err: &toolpath_cursor::CursorError) -> bool {
152 use toolpath_cursor::CursorError;
153 matches!(err, CursorError::Io(e) if e.kind() == std::io::ErrorKind::NotFound)
154 || matches!(err, CursorError::NoHomeDirectory)
155 || matches!(err, CursorError::CursorDataDirectoryNotFound(_))
156 || matches!(err, CursorError::DatabaseNotFound(_))
157}