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")
}
fn home_dir() -> Option<PathBuf> {
#[cfg(windows)]
{
if let Some(p) = std::env::var_os("USERPROFILE") {
return Some(PathBuf::from(p));
}
}
std::env::var_os("HOME").map(PathBuf::from)
}
pub fn record(session_id: &str) {
let Some(home) = home_dir() else { return };
let enabled = channels_enabled_in(&parent_cmdline());
if !enabled {
log::warn!(
"[marshal-shim] channels NOT loaded for this session — live peer \
messages cannot be received (launch needs \
--dangerously-load-development-channels server:marshal). statusline \
will show RECV-OFF."
);
}
write_marker(&home, session_id, enabled);
}
pub fn recv_off(session_id: &str) -> bool {
home_dir()
.and_then(|h| read_marker(&h, session_id))
.is_some_and(|enabled| !enabled)
}
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 marker_path(home: &Path, session_id: &str) -> PathBuf {
config_dir(home).join("channels").join(session_id)
}
pub fn write_marker(home: &Path, session_id: &str, enabled: bool) {
let path = marker_path(home, session_id);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Err(e) = std::fs::write(&path, if enabled { "1" } else { "0" }) {
log::warn!("[marshal-shim] could not write channels marker {path:?}: {e}");
}
}
pub fn read_marker(home: &Path, session_id: &str) -> Option<bool> {
let s = std::fs::read_to_string(marker_path(home, session_id)).ok()?;
match s.trim() {
"1" => Some(true),
"0" => Some(false),
_ => 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",
])));
}
}