1use crate::error::{ErrorCode, ToolError};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Host {
7 Claude,
8 Qwen,
9 Agy,
10 Opencode,
11 Unknown,
12}
13
14pub fn resolve(
18 constant: Host,
19 host_arg: Option<&str>,
20 env_host: Option<&str>,
21) -> Result<Host, ToolError> {
22 match host_arg {
23 Some("opencode") => return Ok(Host::Opencode),
24 Some(other) => {
25 return Err(ToolError::new(
26 ErrorCode::InvalidArgument,
27 format!("unknown --host value '{other}' (only 'opencode' is accepted)"),
28 ))
29 }
30 None => {}
31 }
32 if env_host == Some("opencode") {
33 return Ok(Host::Opencode);
34 }
35 Ok(constant)
36}
37
38impl Host {
39 pub fn binary_name(self) -> &'static str {
40 match self {
41 Host::Claude | Host::Opencode | Host::Unknown => "opencode-ctl-cc-mcp",
42 Host::Qwen => "opencode-ctl-qwen-mcp",
43 Host::Agy => "opencode-ctl-agy-mcp",
44 }
45 }
46
47 pub fn controller(self) -> Option<&'static str> {
48 match self {
49 Host::Claude => Some("claude"),
50 Host::Qwen => Some("qwen"),
51 Host::Agy => Some("agy"),
52 Host::Opencode | Host::Unknown => None,
53 }
54 }
55
56 pub fn workspace_settings_rel(self) -> Option<&'static str> {
57 match self {
58 Host::Claude => Some(".claude/opencode-ctl-mcp-cc.local.md"),
59 Host::Opencode => Some(".opencode/opencode-ctl-mcp-opencode.local.md"),
60 _ => None,
61 }
62 }
63
64 pub fn refuses_lifecycle(self) -> bool {
65 matches!(self, Host::Opencode | Host::Unknown)
66 }
67
68 pub fn offers_launcher(self) -> bool {
69 !matches!(self, Host::Opencode)
70 }
71
72 pub fn slash_form(self, skill: &str) -> String {
73 let suffix = match self {
74 Host::Claude | Host::Unknown => "cc",
75 Host::Qwen => "qwen",
76 Host::Agy => "agy",
77 Host::Opencode => "opencode",
78 };
79 format!("/opencode-ctl-mcp-{suffix}:{skill}")
80 }
81}