use std::path::{Path, PathBuf};
use crate::orchestrator::{absolute_program, resolve_program, ServiceState, ServiceUnit};
pub const TEAMS_ENTRY: &str = "bin/teams.mjs";
pub const TEAMS_PACKAGE: &str = "@volter/supercode-teams";
pub const SERVICE_DIR: &str = "service";
pub const SERVICE_NAME: &str = "dev.volter.supercode-teams-machine";
pub fn connector_service_name(server_id: &str, team_id: &str, context: &str) -> String {
let mut hash = 0xcbf29ce484222325_u64;
for byte in [server_id, team_id, context].join("\0").bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x100000001b3);
}
format!("dev.volter.supercode-teams-connector-{hash:016x}")
}
fn plist_text(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
}
fn service_text(value: &str) -> Result<&str, TeamsError> {
if value.chars().any(char::is_control) {
return Err(TeamsError::Service {
action: "render",
detail: "service parameters cannot contain control characters".into(),
});
}
Ok(value)
}
fn systemd_arg(value: &str) -> String {
format!(
"\"{}\"",
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('%', "%%")
.replace('$', "$$")
)
}
pub fn connector_service_unit(
teams_home: &Path,
supercode_home: &Path,
entry: &Path,
node: &str,
supercode: &Path,
context: &str,
cwd: &Path,
server_id: &str,
team_id: &str,
) -> Result<ServiceUnit, TeamsError> {
let teams_home_text = teams_home.display().to_string();
let supercode_home_text = supercode_home.display().to_string();
let entry_text = entry.display().to_string();
let supercode_text = supercode.display().to_string();
let workspace_text = cwd.display().to_string();
for value in [
teams_home_text.as_str(),
supercode_home_text.as_str(),
entry_text.as_str(),
node,
supercode_text.as_str(),
context,
workspace_text.as_str(),
server_id,
team_id,
] {
service_text(value)?;
}
let label = connector_service_name(server_id, team_id, context);
let path = teams_home
.join(SERVICE_DIR)
.join(format!("{label}.{}", connector_unit_suffix()));
let node = absolute_program(node);
let entry = entry_text;
let workspace = workspace_text;
let home = supercode_home_text;
let supercode = supercode_text;
if cfg!(windows) {
let log_path = teams_home.join(SERVICE_DIR).join(format!("{label}.log"));
return windows_task(
&path,
&label,
&format!("supercode Teams connector ({context})"),
&service_env_path(teams_home, &label),
&[
("SUPERCODE_HOME", home.as_str()),
("SUPERCODE_BIN", supercode.as_str()),
("SUPERCODE_TEAMS_LOG", &log_path.display().to_string()),
],
&node,
&[
entry.as_str(),
"teams",
"connect",
"--foreground",
"--context",
context,
"--cwd",
workspace.as_str(),
],
&workspace,
);
}
if cfg!(target_os = "macos") {
let node = plist_text(&node);
let entry = plist_text(&entry);
let workspace = plist_text(&workspace);
let home = plist_text(&home);
let supercode = plist_text(&supercode);
let context = plist_text(context);
let search_path = plist_text(&service_path());
let text = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>{label}</string>
<key>ProgramArguments</key><array><string>{node}</string><string>{entry}</string><string>teams</string><string>connect</string><string>--context</string><string>{context}</string><string>--cwd</string><string>{workspace}</string></array>
<key>EnvironmentVariables</key><dict><key>SUPERCODE_HOME</key><string>{home}</string><key>SUPERCODE_BIN</key><string>{supercode}</string><key>PATH</key><string>{search_path}</string></dict>
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>{}/service/{label}.out.log</string>
<key>StandardErrorPath</key><string>{}/service/{label}.err.log</string>
</dict></plist>
"#,
plist_text(&teams_home_text),
plist_text(&teams_home_text)
);
Ok(ServiceUnit {
kind: "launchd",
path: path.clone(),
text,
install_command: format!("launchctl bootstrap gui/$(id -u) {}", path.display()),
files: Vec::new(),
})
} else {
let environment_home = systemd_arg(&format!("SUPERCODE_HOME={home}"));
let environment_bin = systemd_arg(&format!("SUPERCODE_BIN={supercode}"));
let environment_path = systemd_arg(&format!("PATH={}", service_path()));
let node = systemd_arg(&node);
let entry = systemd_arg(&entry);
let workspace = systemd_arg(&workspace);
let context_description = context.replace('%', "%%").replace('$', "$$");
let context = systemd_arg(context);
let text = format!("[Unit]\nDescription=supercode Teams connector ({context_description})\nAfter=network.target\n\n[Service]\nEnvironment={environment_home}\nEnvironment={environment_bin}\nEnvironment={environment_path}\nExecStart={node} {entry} teams connect --context {context} --cwd {workspace}\nRestart=on-failure\nKillSignal=SIGTERM\n\n[Install]\nWantedBy=default.target\n");
Ok(ServiceUnit {
kind: "systemd",
path: path.clone(),
text,
install_command: format!(
"systemctl --user link {} && systemctl --user enable --now {label}",
path.display()
),
files: Vec::new(),
})
}
}
fn connector_unit_suffix() -> &'static str {
if cfg!(windows) {
"xml"
} else if cfg!(target_os = "macos") {
"plist"
} else {
"service"
}
}
fn service_env_path(teams_home: &Path, label: &str) -> PathBuf {
teams_home.join(SERVICE_DIR).join(format!("{label}.env"))
}
#[allow(clippy::too_many_arguments)]
fn windows_task(
path: &Path,
label: &str,
description: &str,
env_path: &Path,
env: &[(&str, &str)],
node: &str,
node_args: &[&str],
working_directory: &str,
) -> Result<ServiceUnit, TeamsError> {
let env_text = env
.iter()
.map(|(key, value)| env_file_value(value).map(|value| format!("{key}={value}\n")))
.collect::<Result<String, _>>()?;
let env_flag = format!("--env-file={}", env_path.display());
let mut arguments = vec![node, env_flag.as_str()];
arguments.extend_from_slice(node_args);
for value in arguments.iter().chain([&working_directory]) {
if value.contains('%') {
return Err(TeamsError::Service {
action: "render",
detail: format!("a Windows task cannot carry a path containing `%`: {value}"),
});
}
}
let arguments = arguments
.iter()
.map(|argument| windows_arg(argument))
.collect::<Vec<_>>()
.join(" ");
let user = windows_user();
let conhost = Path::new(&std::env::var("SystemRoot").unwrap_or_else(|_| r"C:\Windows".into()))
.join(r"System32\conhost.exe")
.display()
.to_string();
let text = format!(
r#"<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo><Description>{}</Description></RegistrationInfo>
<Triggers><LogonTrigger><Enabled>true</Enabled><UserId>{}</UserId></LogonTrigger><TimeTrigger><StartBoundary>2000-01-01T00:00:00</StartBoundary><Enabled>true</Enabled><Repetition><Interval>PT1M</Interval><StopAtDurationEnd>false</StopAtDurationEnd></Repetition></TimeTrigger></Triggers>
<Principals><Principal id="Author"><UserId>{}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
<Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure><StartWhenAvailable>true</StartWhenAvailable></Settings>
<Actions Context="Author"><Exec><Command>{}</Command><Arguments>--headless {}</Arguments><WorkingDirectory>{}</WorkingDirectory></Exec></Actions>
</Task>
"#,
xml_text(description),
xml_text(&user),
xml_text(&user),
xml_text(&conhost),
xml_text(&arguments),
xml_text(working_directory),
);
Ok(ServiceUnit {
kind: "schtasks",
path: path.to_path_buf(),
text,
install_command: format!("schtasks /Create /TN {label} /XML {} /F", path.display()),
files: vec![(env_path.to_path_buf(), env_text)],
})
}
fn xml_text(value: &str) -> String {
plist_text(value).replace('"', """)
}
fn windows_arg(value: &str) -> String {
if !value.is_empty() && !value.contains([' ', '\t', '"']) {
return value.to_string();
}
let mut quoted = String::from("\"");
let mut backslashes = 0;
for character in value.chars() {
match character {
'\\' => backslashes += 1,
'"' => {
quoted.push_str(&"\\".repeat(backslashes * 2 + 1));
quoted.push('"');
backslashes = 0;
}
other => {
quoted.push_str(&"\\".repeat(backslashes));
quoted.push(other);
backslashes = 0;
}
}
}
quoted.push_str(&"\\".repeat(backslashes * 2));
quoted.push('"');
quoted
}
fn env_file_value(value: &str) -> Result<String, TeamsError> {
['\'', '`']
.into_iter()
.find(|quote| !value.contains(*quote))
.map(|quote| format!("{quote}{value}{quote}"))
.or_else(|| (!value.contains(['"', '\\'])).then(|| format!("\"{value}\"")))
.ok_or_else(|| TeamsError::Service {
action: "render",
detail: format!("cannot write `{value}` into a service's environment file"),
})
}
fn windows_user() -> String {
let user = std::env::var("USERNAME").unwrap_or_default();
match std::env::var("USERDOMAIN") {
Ok(domain) if !domain.is_empty() => format!("{domain}\\{user}"),
_ => user,
}
}
#[derive(Debug, thiserror::Error)]
pub enum TeamsError {
#[error("no teams entry found (looked for `sdk/teams/{TEAMS_ENTRY}` under: {searched}); install it with `npm install -g {TEAMS_PACKAGE}`")]
NoEntry {
searched: String,
},
#[error("teams service: {action} failed: {detail}")]
Service {
action: &'static str,
detail: String,
},
#[error("teams file `{}`: {source}", path.display())]
File {
path: PathBuf,
source: std::io::Error,
},
}
fn service_path() -> String {
std::env::var("PATH")
.ok()
.filter(|path| !path.trim().is_empty())
.unwrap_or_else(|| "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin".into())
}
pub fn teams_home() -> PathBuf {
if let Ok(home) = std::env::var("SUPERCODE_TEAMS_HOME") {
if !home.is_empty() {
return PathBuf::from(home);
}
}
crate::agent::global_instructions_dir().join("teams")
}
pub fn teams_entry() -> Result<PathBuf, TeamsError> {
let mut searched = Vec::new();
if let Some(explicit) = std::env::var_os("SUPERCODE_TEAMS_ENTRY") {
let path = PathBuf::from(explicit);
if path.is_file() {
return Ok(path);
}
searched.push(path.display().to_string());
}
let mut roots: Vec<PathBuf> = Vec::new();
if let Ok(exe) = std::env::current_exe() {
roots.extend(exe.ancestors().skip(1).take(4).map(Path::to_path_buf));
}
if let Some(workspace) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
roots.push(workspace.to_path_buf());
}
for root in roots {
let candidate = root.join("sdk/teams").join(TEAMS_ENTRY);
if candidate.is_file() {
return Ok(candidate);
}
searched.push(candidate.display().to_string());
}
if let Ok(exe) = std::env::current_exe() {
for modules in exe
.ancestors()
.filter(|dir| dir.file_name().is_some_and(|name| name == "node_modules"))
{
let candidate = modules.join(TEAMS_PACKAGE).join(TEAMS_ENTRY);
if candidate.is_file() {
return Ok(candidate);
}
searched.push(candidate.display().to_string());
}
}
if let Some(global) = global_npm_root() {
let candidate = global.join(TEAMS_PACKAGE).join(TEAMS_ENTRY);
if candidate.is_file() {
return Ok(candidate);
}
searched.push(candidate.display().to_string());
}
Err(TeamsError::NoEntry {
searched: searched.join(", "),
})
}
fn global_npm_root() -> Option<PathBuf> {
let output = std::process::Command::new(resolve_program("npm"))
.args(["root", "-g"])
.stdin(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
let root = text.trim();
if root.is_empty() {
return None;
}
Some(PathBuf::from(root))
}
pub fn service_unit(home: &Path, entry: &Path, node: &str) -> Result<ServiceUnit, TeamsError> {
let home_display = home.display().to_string();
let entry_display = entry.display().to_string();
for value in [home_display.as_str(), entry_display.as_str(), node] {
service_text(value)?;
}
if cfg!(windows) {
let log_path = home.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.log"));
return windows_task(
&home.join(SERVICE_DIR).join(unit_file_name()),
SERVICE_NAME,
&format!("supercode teams machine daemon ({home_display})"),
&service_env_path(home, SERVICE_NAME),
&[
("SUPERCODE_TEAMS_HOME", home_display.as_str()),
("SUPERCODE_TEAMS_LOG", &log_path.display().to_string()),
],
node,
&[entry_display.as_str(), "machine", "start"],
&home_display,
);
}
if cfg!(target_os = "macos") {
let path = home.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
let text = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>{SERVICE_NAME}</string>
<key>ProgramArguments</key>
<array>
<string>{node}</string>
<string>{entry_display}</string>
<string>machine</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>SUPERCODE_TEAMS_HOME</key><string>{home_display}</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>{home_display}/service/teams-machine.out.log</string>
<key>StandardErrorPath</key><string>{home_display}/service/teams-machine.err.log</string>
</dict>
</plist>
"#
);
let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
Ok(ServiceUnit {
kind: "launchd",
path,
text,
install_command: install,
files: Vec::new(),
})
} else {
let path = home
.join(SERVICE_DIR)
.join(format!("{SERVICE_NAME}.service"));
let text = format!(
"[Unit]\n\
Description=supercode teams machine daemon ({home_display})\n\
After=network.target\n\
\n\
[Service]\n\
Environment=SUPERCODE_TEAMS_HOME={home_display}\n\
ExecStart={node} {entry_display} machine start\n\
Restart=on-failure\n\
KillSignal=SIGTERM\n\
\n\
[Install]\n\
WantedBy=default.target\n"
);
let install = format!(
"systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
path.display()
);
Ok(ServiceUnit {
kind: "systemd",
path,
text,
install_command: install,
files: Vec::new(),
})
}
}
pub fn write_unit(unit: &ServiceUnit) -> Result<(), TeamsError> {
if let Some(parent) = unit.path.parent() {
std::fs::create_dir_all(parent).map_err(|source| TeamsError::File {
path: unit.path.clone(),
source,
})?;
}
std::fs::write(&unit.path, &unit.text).map_err(|source| TeamsError::File {
path: unit.path.clone(),
source,
})?;
for (path, text) in &unit.files {
std::fs::write(path, text).map_err(|source| TeamsError::File {
path: path.clone(),
source,
})?;
}
Ok(())
}
fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
let output = std::process::Command::new(program).args(args).output()?;
let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&output.stderr));
Ok((output.status.success(), text.trim().to_string()))
}
#[cfg(target_os = "macos")]
fn gui_domain() -> String {
format!("gui/{}", unsafe { libc::getuid() })
}
pub fn service_status() -> ServiceState {
platform_status()
}
#[cfg(target_os = "macos")]
fn platform_status() -> ServiceState {
let label = SERVICE_NAME.to_string();
let target = format!("{}/{SERVICE_NAME}", gui_domain());
match run_tool("launchctl", &["print", &target]) {
Ok((true, text)) => ServiceState {
kind: "launchd",
label,
installed: true,
pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
},
Ok((false, _)) => ServiceState {
kind: "launchd",
label,
installed: false,
pid: None,
detail: format!("not bootstrapped in {}", gui_domain()),
},
Err(error) => ServiceState {
kind: "launchd",
label,
installed: false,
pid: None,
detail: format!("launchctl unavailable: {error}"),
},
}
}
#[cfg(all(unix, not(target_os = "macos")))]
fn platform_status() -> ServiceState {
let label = SERVICE_NAME.to_string();
match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
Ok((active, text)) => {
let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
.map(|(ok, _)| ok)
.unwrap_or(false);
ServiceState {
kind: "systemd",
label,
installed: active || known,
pid: None,
detail: if text.is_empty() {
"unknown".into()
} else {
text
},
}
}
Err(error) => ServiceState {
kind: "systemd",
label,
installed: false,
pid: None,
detail: format!("systemctl unavailable: {error}"),
},
}
}
#[cfg(not(unix))]
fn platform_status() -> ServiceState {
named_service_status(SERVICE_NAME)
}
#[cfg(target_os = "macos")]
fn field_of(text: &str, key: &str) -> Option<String> {
text.lines()
.find_map(|line| line.trim().strip_prefix(key))
.map(|value| value.trim().to_string())
}
fn unit_file_name() -> String {
if cfg!(windows) {
format!("{SERVICE_NAME}.xml")
} else if cfg!(target_os = "macos") {
format!("{SERVICE_NAME}.plist")
} else {
format!("{SERVICE_NAME}.service")
}
}
pub fn install_service(
home: &Path,
entry: &Path,
node: &str,
) -> Result<(ServiceUnit, ServiceState), TeamsError> {
let existing = service_status();
if existing.installed {
return Err(TeamsError::Service {
action: "install",
detail: format!(
"`{}` is already installed ({}); `supercode teams machine uninstall` first",
existing.label, existing.detail
),
});
}
let unit = service_unit(home, entry, &absolute_program(node))?;
write_unit(&unit)?;
platform_install(&unit)?;
Ok((unit, service_status()))
}
pub fn install_connector_service(
unit: &ServiceUnit,
label: &str,
) -> Result<ServiceState, TeamsError> {
let existing = named_service_status(label);
if unit.path.exists() {
let old = std::fs::read_to_string(&unit.path).map_err(|source| TeamsError::File {
path: unit.path.clone(),
source,
})?;
let same = old == unit.text
&& unit
.files
.iter()
.all(|(path, text)| std::fs::read_to_string(path).is_ok_and(|old| &old == text));
if same && existing.installed {
return Ok(existing);
}
if !same && existing.installed {
named_platform_uninstall(label)?;
}
} else if existing.installed {
return Err(TeamsError::Service {
action: "install",
detail: format!(
"service manager already owns `{label}` without its expected unit file"
),
});
}
write_unit(unit)?;
named_platform_install(unit, label)?;
Ok(named_service_status(label))
}
pub fn register_message_tools(supercode: &Path) -> Vec<String> {
let program = supercode.display().to_string();
let mut report = Vec::new();
for (harness, get, add) in [
(
"claude",
vec!["mcp", "get", "supercode"],
vec![
"mcp",
"add",
"--scope",
"user",
"supercode",
"--",
&program,
"message",
"mcp",
],
),
(
"codex",
vec!["mcp", "get", "supercode"],
vec!["mcp", "add", "supercode", "--", &program, "message", "mcp"],
),
] {
let run = |args: &[&str]| {
std::process::Command::new(resolve_program(harness))
.args(args)
.stdin(std::process::Stdio::null())
.output()
};
match run(&get) {
Err(_) => report.push(format!("{harness}: not installed")),
Ok(found) if found.status.success() => {
report.push(format!("{harness}: already has supercode's tools"))
}
Ok(_) => match run(&add) {
Ok(added) if added.status.success() => report.push(format!(
"{harness}: supercode's tools added (new sessions load them)"
)),
Ok(added) => report.push(format!(
"{harness}: could not add supercode's tools: {}",
String::from_utf8_lossy(&added.stderr).trim()
)),
Err(error) => report.push(format!(
"{harness}: could not add supercode's tools: {error}"
)),
},
}
}
report
}
pub fn uninstall_connector_service(
teams_home: &Path,
label: &str,
) -> Result<ServiceState, TeamsError> {
named_platform_uninstall(label)?;
let unit = teams_home
.join(SERVICE_DIR)
.join(format!("{label}.{}", connector_unit_suffix()));
for path in [unit, service_env_path(teams_home, label)] {
match std::fs::remove_file(&path) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(source) => return Err(TeamsError::File { path, source }),
}
}
Ok(named_service_status(label))
}
pub fn connector_service_status(label: &str) -> ServiceState {
named_service_status(label)
}
#[cfg(target_os = "macos")]
fn named_service_status(label: &str) -> ServiceState {
let target = format!("{}/{label}", gui_domain());
match run_tool("launchctl", &["print", &target]) {
Ok((true, text)) => ServiceState {
kind: "launchd",
label: label.into(),
installed: true,
pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
},
Ok((false, _)) => ServiceState {
kind: "launchd",
label: label.into(),
installed: false,
pid: None,
detail: format!("not bootstrapped in {}", gui_domain()),
},
Err(error) => ServiceState {
kind: "launchd",
label: label.into(),
installed: false,
pid: None,
detail: format!("launchctl unavailable: {error}"),
},
}
}
#[cfg(all(unix, not(target_os = "macos")))]
fn named_service_status(label: &str) -> ServiceState {
match run_tool("systemctl", &["--user", "is-active", label]) {
Ok((active, text)) => {
let known = run_tool("systemctl", &["--user", "is-enabled", label])
.map(|(ok, _)| ok)
.unwrap_or(false);
ServiceState {
kind: "systemd",
label: label.into(),
installed: active || known,
pid: None,
detail: if text.is_empty() {
"unknown".into()
} else {
text
},
}
}
Err(error) => ServiceState {
kind: "systemd",
label: label.into(),
installed: false,
pid: None,
detail: format!("systemctl unavailable: {error}"),
},
}
}
#[cfg(not(unix))]
fn named_service_status(label: &str) -> ServiceState {
match run_tool("schtasks", &["/Query", "/TN", label, "/FO", "CSV", "/NH"]) {
Ok((true, text)) => ServiceState {
kind: "schtasks",
label: label.into(),
installed: true,
pid: None,
detail: text
.lines()
.next()
.and_then(|line| line.rsplit(',').next())
.map(|state| state.trim_matches('"').to_string())
.filter(|state| !state.is_empty())
.unwrap_or_else(|| "registered".into()),
},
Ok((false, _)) => ServiceState {
kind: "schtasks",
label: label.into(),
installed: false,
pid: None,
detail: "no scheduled task".into(),
},
Err(error) => ServiceState {
kind: "schtasks",
label: label.into(),
installed: false,
pid: None,
detail: format!("schtasks unavailable: {error}"),
},
}
}
#[cfg(target_os = "macos")]
fn named_platform_install(unit: &ServiceUnit, _label: &str) -> Result<(), TeamsError> {
platform_install(unit)
}
#[cfg(all(unix, not(target_os = "macos")))]
fn named_platform_install(unit: &ServiceUnit, label: &str) -> Result<(), TeamsError> {
let path = unit.path.display().to_string();
for args in [
vec!["--user", "link", path.as_str()],
vec!["--user", "enable", "--now", label],
] {
let (ok, text) = run_tool("systemctl", &args).map_err(|error| TeamsError::Service {
action: "install",
detail: format!("systemctl: {error}"),
})?;
if !ok {
return Err(TeamsError::Service {
action: "install",
detail: format!("systemctl {}: {text}", args.join(" ")),
});
}
}
Ok(())
}
#[cfg(not(unix))]
fn named_platform_install(unit: &ServiceUnit, label: &str) -> Result<(), TeamsError> {
let task = unit.path.with_extension("utf16.xml");
let bytes: Vec<u8> = [0xFF, 0xFE]
.into_iter()
.chain(unit.text.encode_utf16().flat_map(u16::to_le_bytes))
.collect();
std::fs::write(&task, bytes).map_err(|source| TeamsError::File {
path: task.clone(),
source,
})?;
let task_path = task.display().to_string();
let result = [
vec!["/Create", "/TN", label, "/XML", task_path.as_str(), "/F"],
vec!["/Run", "/TN", label],
]
.iter()
.try_for_each(|args| {
let (ok, text) = run_tool("schtasks", args).map_err(|error| TeamsError::Service {
action: "install",
detail: format!("schtasks: {error}"),
})?;
if ok {
Ok(())
} else {
Err(TeamsError::Service {
action: "install",
detail: format!("schtasks {}: {text}", args[0]),
})
}
});
let _ = std::fs::remove_file(&task);
result
}
#[cfg(target_os = "macos")]
fn named_platform_uninstall(label: &str) -> Result<(), TeamsError> {
let target = format!("{}/{label}", gui_domain());
let (ok, text) =
run_tool("launchctl", &["bootout", &target]).map_err(|error| TeamsError::Service {
action: "uninstall",
detail: format!("launchctl: {error}"),
})?;
if !ok && !text.contains("No such process") && !text.contains("not find") {
return Err(TeamsError::Service {
action: "uninstall",
detail: format!("launchctl bootout {target}: {text}"),
});
}
for _ in 0..50 {
if !named_service_status(label).installed {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
Ok(())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn named_platform_uninstall(label: &str) -> Result<(), TeamsError> {
let _ = run_tool("systemctl", &["--user", "disable", "--now", label]);
Ok(())
}
#[cfg(not(unix))]
fn named_platform_uninstall(label: &str) -> Result<(), TeamsError> {
let (ok, text) = run_tool("schtasks", &["/Delete", "/TN", label, "/F"]).map_err(|error| {
TeamsError::Service {
action: "uninstall",
detail: format!("schtasks: {error}"),
}
})?;
if !ok && named_service_status(label).installed {
return Err(TeamsError::Service {
action: "uninstall",
detail: format!("schtasks /Delete: {text}"),
});
}
let env_path = service_env_path(&teams_home(), label);
let stopped = std::process::Command::new("powershell.exe")
.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"Get-CimInstance Win32_Process -Filter \"Name='node.exe' OR Name='conhost.exe'\" | Where-Object { $_.CommandLine -and $_.CommandLine.Contains($env:SUPERCODE_SERVICE_ENV_FILE) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }",
])
.env("SUPERCODE_SERVICE_ENV_FILE", env_path.display().to_string())
.stdin(std::process::Stdio::null())
.output();
match stopped {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => Err(TeamsError::Service {
action: "uninstall",
detail: format!(
"the task is gone, but what it started may still run: {}",
String::from_utf8_lossy(&output.stderr).trim()
),
}),
Err(error) => Err(TeamsError::Service {
action: "uninstall",
detail: format!(
"the task is gone, but what it started may still run: powershell: {error}"
),
}),
}
}
#[cfg(target_os = "macos")]
fn platform_install(unit: &ServiceUnit) -> Result<(), TeamsError> {
let path = unit.path.display().to_string();
let (ok, text) =
run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
TeamsError::Service {
action: "install",
detail: format!("launchctl: {error}"),
}
})?;
if !ok {
return Err(TeamsError::Service {
action: "install",
detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
});
}
Ok(())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn platform_install(unit: &ServiceUnit) -> Result<(), TeamsError> {
let path = unit.path.display().to_string();
for args in [
vec!["--user", "link", path.as_str()],
vec!["--user", "enable", "--now", SERVICE_NAME],
] {
let (ok, text) = run_tool("systemctl", &args).map_err(|error| TeamsError::Service {
action: "install",
detail: format!("systemctl: {error}"),
})?;
if !ok {
return Err(TeamsError::Service {
action: "install",
detail: format!("systemctl {}: {text}", args.join(" ")),
});
}
}
Ok(())
}
#[cfg(not(unix))]
fn platform_install(unit: &ServiceUnit) -> Result<(), TeamsError> {
named_platform_install(unit, SERVICE_NAME)
}
pub fn uninstall_service(home: &Path) -> Result<ServiceState, TeamsError> {
platform_uninstall()?;
let unit_path = home.join(SERVICE_DIR).join(unit_file_name());
for path in [unit_path, service_env_path(home, SERVICE_NAME)] {
match std::fs::remove_file(&path) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(source) => return Err(TeamsError::File { path, source }),
}
}
let mut state = service_status();
for _ in 0..40 {
if !state.installed {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
state = service_status();
}
Ok(state)
}
#[cfg(target_os = "macos")]
fn platform_uninstall() -> Result<(), TeamsError> {
let target = format!("{}/{SERVICE_NAME}", gui_domain());
let (ok, text) =
run_tool("launchctl", &["bootout", &target]).map_err(|error| TeamsError::Service {
action: "uninstall",
detail: format!("launchctl: {error}"),
})?;
if !ok && !text.contains("No such process") && !text.contains("not find") {
return Err(TeamsError::Service {
action: "uninstall",
detail: format!("launchctl bootout {target}: {text}"),
});
}
Ok(())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn platform_uninstall() -> Result<(), TeamsError> {
let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
Ok(())
}
#[cfg(not(unix))]
fn platform_uninstall() -> Result<(), TeamsError> {
named_platform_uninstall(SERVICE_NAME)
}
#[cfg(test)]
mod connector_service_tests {
use super::*;
#[test]
fn connector_unit_is_context_scoped_and_contains_no_credential() {
let unit = connector_service_unit(
Path::new("/tmp/teams home"),
Path::new("/tmp/supercode home"),
Path::new("/tmp/sdk/teams/bin/teams.mjs"),
"/usr/bin/node",
Path::new("/tmp/bin/supercode"),
"work",
Path::new("/tmp/project with spaces"),
"srv_1",
"team_1",
)
.unwrap();
assert!(unit.text.contains("teams"));
assert!(unit.text.contains("connect"));
assert!(unit.text.contains("work"));
assert!(!unit.text.contains("credential"));
assert!(unit
.path
.file_name()
.unwrap()
.to_string_lossy()
.contains(&connector_service_name("srv_1", "team_1", "work")));
}
#[test]
fn connector_labels_separate_context_and_team() {
assert_ne!(
connector_service_name("srv", "team-a", "work"),
connector_service_name("srv", "team-b", "work")
);
assert_ne!(
connector_service_name("srv", "team-a", "work"),
connector_service_name("srv", "team-a", "personal")
);
}
#[test]
fn connector_unit_rejects_newlines_and_escapes_service_syntax() {
let unsafe_unit = connector_service_unit(
Path::new("/tmp/teams"),
Path::new("/tmp/home"),
Path::new("/tmp/entry"),
"/usr/bin/node",
Path::new("/tmp/supercode"),
"bad\ncontext",
Path::new("/tmp/work"),
"srv",
"team",
);
assert!(unsafe_unit.is_err());
let render = |entry: &str, cwd: &str| {
connector_service_unit(
Path::new("/tmp/teams & logs"),
Path::new("/tmp/home $x"),
Path::new(entry),
"/usr/bin/node",
Path::new("/tmp/super\"code"),
"work",
Path::new(cwd),
"srv",
"team",
)
};
if cfg!(windows) {
assert!(render("/tmp/entry %i", "/tmp/a & b $").is_err());
let unit = render("/tmp/entry", "/tmp/a & b $").unwrap();
assert!(unit.text.contains("--cwd "/tmp/a & b $""));
let (env_path, env_text) = &unit.files[0];
assert!(env_path.ends_with(format!(
"{}.env",
connector_service_name("srv", "team", "work")
)));
assert!(env_text.contains("SUPERCODE_BIN='/tmp/super\"code'\n"));
assert!(!unit.text.contains("super\"code"));
} else if cfg!(target_os = "macos") {
let unit = render("/tmp/entry %i", "/tmp/a & b % $").unwrap();
assert!(unit.text.contains("&"));
} else {
let unit = render("/tmp/entry %i", "/tmp/a & b % $").unwrap();
assert!(unit.text.contains("%%"));
assert!(unit.text.contains("$$"));
assert!(unit.text.contains("\\\""));
}
}
#[test]
fn machine_unit_runs_machine_start_against_its_home() {
let unit = service_unit(
Path::new("/tmp/teams & home"),
Path::new("/tmp/sdk/teams/bin/teams.mjs"),
"/usr/bin/node",
)
.unwrap();
assert!(unit.text.contains("machine"));
assert!(unit.text.contains("start"));
assert!(unit.path.ends_with(format!("service/{}", unit_file_name())));
assert!(service_unit(Path::new("/tmp/bad\nhome"), Path::new("/e"), "node").is_err());
if cfg!(windows) {
assert_eq!(unit.kind, "schtasks");
let (env_path, env_text) = &unit.files[0];
assert!(env_path.ends_with(format!("{SERVICE_NAME}.env")));
assert!(env_text.contains("SUPERCODE_TEAMS_HOME='/tmp/teams & home'\n"));
assert!(env_text.contains(&format!("{SERVICE_NAME}.log'\n")));
assert!(unit.text.contains("--headless"));
assert!(unit.text.contains("PT1M"));
assert!(unit
.text
.contains("<WorkingDirectory>/tmp/teams & home</WorkingDirectory>"));
assert!(unit
.text
.contains(r""--env-file=/tmp/teams & home\service\"));
assert!(unit.text.contains("teams.mjs machine start</Arguments>"));
assert!(service_unit(Path::new("/tmp/100%"), Path::new("/e"), "node").is_err());
}
}
#[test]
fn windows_arguments_split_back_out_whole() {
assert_eq!(windows_arg("plain"), "plain");
assert_eq!(windows_arg(""), "\"\"");
assert_eq!(
windows_arg(r"C:\Program Files\nodejs\node.exe"),
r#""C:\Program Files\nodejs\node.exe""#
);
assert_eq!(windows_arg(r#"say "hi""#), r#""say \"hi\"""#);
assert_eq!(windows_arg(r#"a\"b"#), r#""a\\\"b""#);
assert_eq!(windows_arg(r"C:\my dir\"), r#""C:\my dir\\""#);
}
#[test]
fn env_file_values_are_quoted_literally() {
assert_eq!(env_file_value(r"C:\new\tab").unwrap(), r"'C:\new\tab'");
assert_eq!(env_file_value("O'Brien").unwrap(), "`O'Brien`");
assert_eq!(env_file_value("it's `x`").unwrap(), "\"it's `x`\"");
assert!(env_file_value(r"it's `x` \n").is_err());
}
}