Skip to main content

opencode_ctl/
host.rs

1//! Host identity: which agent fronts the engine and what it may do.
2
3use 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
14/// Narrow the compiled-in host constant. Either `host_arg` or `env_host` being
15/// `Some("opencode")` selects `Opencode`; any other `host_arg` is rejected;
16/// other env values are ignored.
17pub 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}