use std::path::{Path, PathBuf};
pub fn channels_enabled_in(parent_cmdline: &[String]) -> bool {
let joined = parent_cmdline.join(" ");
joined.contains("--dangerously-load-development-channels") && joined.contains("marshal")
}
pub fn detect() -> Option<bool> {
let cmd = parent_cmdline();
if cmd.is_empty() {
return None;
}
Some(channels_enabled_in(&cmd))
}
pub fn recv_off_live() -> bool {
matches!(detect_cached(), Some(false))
}
fn detect_cached() -> Option<bool> {
let Some((ppid, token)) = super::session_discovery::parent_identity() else {
return detect();
};
if let Some(home) = home_dir() {
if let Some(cached) = read_cache(&home, ppid, token) {
return Some(cached);
}
let detected = detect();
if let Some(flag) = detected {
write_cache(&home, ppid, token, flag);
}
return detected;
}
detect()
}
fn config_dir(home: &Path) -> PathBuf {
#[cfg(windows)]
{
if let Some(appdata) = std::env::var_os("APPDATA").filter(|s| !s.is_empty()) {
return PathBuf::from(appdata).join("marshal");
}
}
#[cfg(unix)]
{
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME").filter(|s| !s.is_empty()) {
return PathBuf::from(xdg).join("marshal");
}
}
home.join(".config").join("marshal")
}
fn cache_path(home: &Path, ppid: u32) -> PathBuf {
config_dir(home)
.join("channels-cache")
.join(ppid.to_string())
}
fn read_cache(home: &Path, ppid: u32, token: u128) -> Option<bool> {
let s = std::fs::read_to_string(cache_path(home, ppid)).ok()?;
let (tok, flag) = s.trim().split_once(':')?;
if tok.parse::<u128>().ok()? != token {
return None;
}
match flag {
"1" => Some(true),
"0" => Some(false),
_ => None,
}
}
fn write_cache(home: &Path, ppid: u32, token: u128, flag: bool) {
let path = cache_path(home, ppid);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let body = format!("{token}:{}", if flag { "1" } else { "0" });
if let Err(e) = std::fs::write(&path, body) {
log::warn!("[marshal-shim] could not write channels cache {path:?}: {e}");
}
}
#[cfg(unix)]
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME").map(PathBuf::from)
}
#[cfg(windows)]
fn home_dir() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("USERPROFILE") {
return Some(PathBuf::from(p));
}
std::env::var_os("HOME").map(PathBuf::from)
}
#[cfg(not(any(unix, windows)))]
fn home_dir() -> Option<PathBuf> {
None
}
pub fn parent_cmdline() -> Vec<String> {
#[cfg(target_os = "linux")]
{
let ppid = unsafe { libc::getppid() } as u32;
if let Ok(raw) = std::fs::read(format!("/proc/{ppid}/cmdline")) {
return raw
.split(|&b| b == 0)
.filter(|s| !s.is_empty())
.map(|s| String::from_utf8_lossy(s).into_owned())
.collect();
}
Vec::new()
}
#[cfg(windows)]
{
windows_parent_cmdline().unwrap_or_default()
}
#[cfg(not(any(target_os = "linux", windows)))]
{
Vec::new()
}
}
#[cfg(windows)]
fn windows_parent_cmdline() -> Option<Vec<String>> {
use std::os::windows::process::CommandExt;
let ppid = super::session_discovery::windows_parent_pid()?;
let out = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-NonInteractive",
"-Command",
&format!("(Get-CimInstance Win32_Process -Filter \"ProcessId={ppid}\").CommandLine"),
])
.creation_flags(0x08000000)
.output()
.ok()?;
if !out.status.success() {
return None;
}
let line = String::from_utf8_lossy(&out.stdout).trim().to_string();
if line.is_empty() {
return None;
}
Some(vec![line])
}
#[cfg(test)]
mod tests {
use super::channels_enabled_in;
fn v(s: &[&str]) -> Vec<String> {
s.iter().map(|x| x.to_string()).collect()
}
#[test]
fn flag_with_server_marshal_is_enabled() {
assert!(channels_enabled_in(&v(&[
"claude",
"--dangerously-load-development-channels",
"server:marshal",
])));
}
#[test]
fn flag_with_plugin_marshal_is_enabled() {
assert!(channels_enabled_in(&v(&[
"claude",
"--dangerously-load-development-channels",
"plugin:marshal-shim@marshal",
])));
}
#[test]
fn bare_claude_is_disabled() {
assert!(!channels_enabled_in(&v(&["claude"])));
}
#[test]
fn flag_for_a_different_server_is_disabled() {
assert!(!channels_enabled_in(&v(&[
"claude",
"--dangerously-load-development-channels",
"server:something-else",
])));
}
#[test]
fn windows_single_line_cmdline_matches() {
assert!(channels_enabled_in(&v(&[
"\"C:\\Users\\admin\\.local\\bin\\claude.exe\" --dangerously-load-development-channels server:marshal",
])));
}
}