supercode-cli 0.4.7

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! Tmux is an attachable process supervisor, never a runtime authority.
//!
//! The owner process registers with the authenticated SDK registry. A second
//! tmux window runs the ordinary registry-backed terminal frontend. Killing
//! that frontend window cannot kill the owner or alter persisted data.

use std::ffi::{OsStr, OsString};
use std::fs::{self, OpenOptions};
use std::io::Write;
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(unix)]
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};

const CHILD_ENV: &str = "SUPERCODE_TMUX_CHILD";
const TMUX_ENV: &str = "SUPERCODE_TMUX_SESSION";

#[derive(Serialize, Deserialize)]
struct LaunchSpec {
    executable: Vec<u8>,
    arguments: Vec<Vec<u8>>,
    environment: Vec<(Vec<u8>, Vec<u8>)>,
}

pub(crate) fn is_child() -> bool {
    std::env::var_os(CHILD_ENV).is_some()
}

pub(crate) fn available() -> bool {
    cfg!(unix)
        && Command::new("tmux")
            .arg("-V")
            .output()
            .is_ok_and(|output| output.status.success())
}

pub(crate) fn predictable_name(kind: &str, identity: &str) -> String {
    let slug = identity
        .chars()
        .map(|character| {
            if character.is_ascii_alphanumeric() {
                character.to_ascii_lowercase()
            } else {
                '-'
            }
        })
        .collect::<String>()
        .split('-')
        .filter(|part| !part.is_empty())
        .take(4)
        .collect::<Vec<_>>()
        .join("-");
    let slug = if slug.is_empty() { "session" } else { &slug };
    format!(
        "supercode-{kind}-{}-{:08x}",
        slug.chars().take(28).collect::<String>(),
        stable_hash(identity) as u32
    )
}

pub(crate) fn session_exists(name: &str) -> bool {
    Command::new("tmux")
        .args(["has-session", "-t", name])
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .is_ok_and(|status| status.success())
}

pub(crate) fn registered_runtime(name: &str) -> Result<Option<supercode::LiveRuntimeRecord>> {
    Ok(supercode::list_live_runtimes()?.into_iter().find(|record| {
        matches!(
            &record.metadata.supervisor,
            Some(supercode::LiveRuntimeSupervisor::Tmux { session_name })
                if session_name == name
        )
    }))
}

/// Remove only an unregistered tmux container. Runtime receipts and all
/// persistence paths are intentionally outside this operation.
pub(crate) fn reconcile_stale(name: &str) -> Result<()> {
    if session_exists(name) && registered_runtime(name)?.is_none() {
        let status = Command::new("tmux")
            .args(["kill-session", "-t", name])
            .status()
            .context("removing stale tmux supervisor")?;
        if !status.success() {
            bail!("could not remove stale tmux supervisor `{name}`");
        }
    }
    Ok(())
}

/// Launch the current invocation as a detached owner, wait for its SDK
/// receipt, then add and select a registry-backed frontend window.
pub(crate) async fn launch(
    tmux_name: &str,
    api_key: Option<&str>,
    server_token: Option<&str>,
) -> Result<supercode::LiveRuntimeRecord> {
    if session_exists(tmux_name) {
        bail!("tmux session `{tmux_name}` already exists without a matching live runtime");
    }
    let executable = std::env::current_exe().context("resolving the supercode executable")?;
    let arguments = sanitized_arguments();
    let mut environment = captured_environment();
    environment.extend([
        (CHILD_ENV.as_bytes().to_vec(), b"1".to_vec()),
        (TMUX_ENV.as_bytes().to_vec(), tmux_name.as_bytes().to_vec()),
    ]);
    if let Some(value) = api_key {
        environment.push((
            b"SUPERCODE_SUPERVISED_API_KEY".to_vec(),
            value.as_bytes().to_vec(),
        ));
    }
    if let Some(value) = server_token {
        environment.push((
            b"SUPERCODE_SERVER_TOKEN".to_vec(),
            value.as_bytes().to_vec(),
        ));
    }
    let spec_path = write_spec(&LaunchSpec {
        executable: os_bytes(executable.as_os_str()),
        arguments: arguments.iter().map(|value| os_bytes(value)).collect(),
        environment,
    })?;
    let status = Command::new("tmux")
        .args(["new-session", "-d", "-s", tmux_name, "-n", "runtime"])
        .arg(&executable)
        .arg("__supervised-child")
        .arg(&spec_path)
        .status()
        .context("starting tmux supervisor")?;
    if !status.success() {
        let _ = fs::remove_file(&spec_path);
        bail!("tmux could not start `{tmux_name}`");
    }

    let deadline = tokio::time::Instant::now() + Duration::from_secs(20);
    let record = loop {
        if let Some(record) = registered_runtime(tmux_name)? {
            break record;
        }
        if !session_exists(tmux_name) {
            bail!("supervised runtime exited before registering; rerun with --no-tmux to inspect the startup error");
        }
        if tokio::time::Instant::now() >= deadline {
            let _ = Command::new("tmux")
                .args(["kill-session", "-t", tmux_name])
                .status();
            bail!("timed out waiting for supervised runtime `{tmux_name}` to register");
        }
        tokio::time::sleep(Duration::from_millis(50)).await;
    };

    let frontend_spec_path = write_spec(&LaunchSpec {
        executable: os_bytes(executable.as_os_str()),
        arguments: [
            OsString::from("sessions"),
            OsString::from("attach"),
            OsString::from(&record.runtime_session_id),
            OsString::from("--take-control"),
        ]
        .iter()
        .map(|value| os_bytes(value))
        .collect(),
        environment: captured_environment(),
    })?;
    let frontend_status = Command::new("tmux")
        .args(["new-window", "-d", "-t", tmux_name, "-n", "frontend"])
        .arg(&executable)
        .arg("__supervised-child")
        .arg(&frontend_spec_path)
        .status()
        .context("starting tmux frontend")?;
    if !frontend_status.success() {
        let _ = fs::remove_file(frontend_spec_path);
        bail!("runtime started, but tmux could not create its frontend window");
    }
    let _ = Command::new("tmux")
        .args(["select-window", "-t", &format!("{tmux_name}:frontend")])
        .status();
    Ok(record)
}

/// Execute a private launch spec. The spec is deleted before the real owner
/// starts, and credentials move through environment variables rather than
/// process arguments or tmux's command listing.
pub(crate) fn run_child(spec_path: &Path) -> Result<()> {
    let bytes = fs::read(spec_path).context("reading supervised launch spec")?;
    let _ = fs::remove_file(spec_path);
    let spec: LaunchSpec =
        serde_json::from_slice(&bytes).context("parsing supervised launch spec")?;
    let executable = os_string(spec.executable);
    let arguments = spec
        .arguments
        .into_iter()
        .map(os_string)
        .collect::<Vec<_>>();
    let mut command = Command::new(executable);
    command.args(arguments);
    for (key, value) in spec.environment {
        command.env(os_string(key), os_string(value));
    }
    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        Err(command.exec()).context("executing supervised runtime owner")
    }
    #[cfg(not(unix))]
    {
        let status = command
            .status()
            .context("executing supervised runtime owner")?;
        if !status.success() {
            bail!("supervised runtime owner exited with {status}");
        }
        Ok(())
    }
}

fn sanitized_arguments() -> Vec<OsString> {
    let mut output = Vec::new();
    let mut input = std::env::args_os().skip(1);
    while let Some(argument) = input.next() {
        let text = argument.to_string_lossy();
        if text == "--api-key" || text == "--token" {
            let _ = input.next();
            continue;
        }
        if text.starts_with("--api-key=") || text.starts_with("--token=") {
            continue;
        }
        output.push(argument);
    }
    output
}

fn write_spec(spec: &LaunchSpec) -> Result<PathBuf> {
    let directory = supervisor_directory();
    fs::create_dir_all(&directory)?;
    #[cfg(unix)]
    fs::set_permissions(&directory, fs::Permissions::from_mode(0o700))?;
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_nanos();
    let path = directory.join(format!("launch-{}-{nonce}.json", std::process::id()));
    let mut options = OpenOptions::new();
    options.write(true).create_new(true);
    #[cfg(unix)]
    options.mode(0o600);
    let mut file = options.open(&path)?;
    file.write_all(&serde_json::to_vec(spec)?)?;
    file.sync_all()?;
    Ok(path)
}

fn supervisor_directory() -> PathBuf {
    if let Some(home) = std::env::var_os("SUPERCODE_HOME").filter(|value| !value.is_empty()) {
        return PathBuf::from(home).join("supervisor");
    }
    if let Some(home) = std::env::var_os("XDG_CONFIG_HOME").filter(|value| !value.is_empty()) {
        return PathBuf::from(home).join("supercode/supervisor");
    }
    PathBuf::from(std::env::var_os("HOME").unwrap_or_else(|| OsString::from(".")))
        .join(".config/supercode/supervisor")
}

fn captured_environment() -> Vec<(Vec<u8>, Vec<u8>)> {
    std::env::vars_os()
        .map(|(key, value)| (os_bytes(&key), os_bytes(&value)))
        .collect()
}

fn stable_hash(value: &str) -> u64 {
    value.bytes().fold(0xcbf29ce484222325, |hash, byte| {
        (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3)
    })
}

#[cfg(unix)]
fn os_bytes(value: &OsStr) -> Vec<u8> {
    value.as_bytes().to_vec()
}

#[cfg(not(unix))]
fn os_bytes(value: &OsStr) -> Vec<u8> {
    value.to_string_lossy().as_bytes().to_vec()
}

#[cfg(unix)]
fn os_string(value: Vec<u8>) -> OsString {
    OsString::from_vec(value)
}

#[cfg(not(unix))]
fn os_string(value: Vec<u8>) -> OsString {
    OsString::from(String::from_utf8_lossy(&value).into_owned())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn predictable_names_are_stable_sanitized_and_distinct() {
        let first = predictable_name("claude", "session/A strange id");
        assert_eq!(first, predictable_name("claude", "session/A strange id"));
        assert!(first.starts_with("supercode-claude-session-a-strange-id-"));
        assert_ne!(first, predictable_name("claude", "session/A strange id 2"));
    }
}