supercode-cli 0.4.15

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! ORC-11 CLI acceptance: `supercode orchestrator import --from claude-session
//! <id>` compiles a Claude session's carried schedules into orchestrator jobs.
//!
//! Everything runs through the product's own doors and its own environment
//! variables (`CLAUDE_CONFIG_DIR`, `SUPERCODE_ORCHESTRATOR_HOME`) against the
//! committed Claude fixture home, offline. The import writes a file; nothing
//! fires, and no orchestrator daemon is running.

use std::path::PathBuf;
use std::process::{Command, Output};

/// The fixture session carrying one recurring cron and one pending wakeup.
const SESSION: &str = "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f";

fn bin() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}

fn fixtures() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../harness/tests/fixtures")
}

fn temp_root(label: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!(
        "orc11-import-{label}-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

fn run(root: &PathBuf, args: &[&str]) -> Output {
    Command::new(bin())
        .env("CLAUDE_CONFIG_DIR", fixtures().join("claude_jobs_home"))
        .env("SUPERCODE_ORCHESTRATOR_HOME", root)
        // The other job stores must stay out of the picture: this test is
        // about one home.
        .env("HERMES_HOME", root.join("no-hermes"))
        .env("OPENCLAW_STATE_DIR", root.join("no-openclaw"))
        .args(args)
        .output()
        .expect("supercode binary runs")
}

fn json_of(output: &Output) -> serde_json::Value {
    assert!(
        output.status.success(),
        "command failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    serde_json::from_slice(&output.stdout).expect("stdout is JSON")
}

#[test]
fn import_compiles_the_sessions_cron_and_wakeup_into_the_orchestrators_store() {
    let root = temp_root("compile");
    let result = json_of(&run(
        &root,
        &[
            "orchestrator",
            "import",
            "--from",
            "claude-session",
            SESSION,
            "--json",
        ],
    ));
    assert_eq!(result["source"]["kind"], "claude-session");
    assert_eq!(result["source"]["id"], SESSION);
    // The verb writes a store. It starts nothing.
    assert_eq!(result["fired"], 0);
    assert_eq!(
        result["added"]
            .as_array()
            .unwrap()
            .iter()
            .map(|id| id.as_str().unwrap())
            .collect::<Vec<_>>(),
        vec![
            format!("{SESSION}-release-watch"),
            format!("{SESSION}-toolu_wake_recheck"),
        ]
    );

    let store = root.join("cron/jobs.json");
    assert!(store.exists(), "{}", store.display());
    let jobs: Vec<serde_json::Value> =
        serde_json::from_str(&std::fs::read_to_string(&store).unwrap()).unwrap();
    assert_eq!(jobs.len(), 2);

    // The cron carries Claude's own expression, and the zone Claude's record
    // does NOT carry is stated rather than assumed.
    assert_eq!(jobs[0]["schedule"]["kind"], "cron");
    assert_eq!(jobs[0]["schedule"]["expr"], "*/10 * * * *");
    assert_eq!(jobs[0]["schedule"]["tz"], "UTC");
    assert_eq!(
        jobs[0]["prompt"],
        "Check the release branch for new failures."
    );
    assert_eq!(jobs[0]["workdir"], "/workspace/project");
    assert_eq!(jobs[0]["enabled"], true);
    // Armed: the orchestrator never fires a job whose `next_run_at` is null.
    assert!(jobs[0]["next_run_at"].is_string(), "{}", jobs[0]);

    assert_eq!(jobs[1]["schedule"]["kind"], "once");
    assert!(jobs[1]["schedule"]["run_at"].is_string(), "{}", jobs[1]);
    assert_eq!(
        jobs[1]["prompt"],
        "Re-read the release branch status and report."
    );

    std::fs::remove_dir_all(root).ok();
}

#[test]
fn the_imported_jobs_are_listed_under_the_orchestrator_and_still_under_claude_code() {
    let root = temp_root("listing");
    assert!(run(
        &root,
        &[
            "orchestrator",
            "import",
            "--from",
            "claude-session",
            SESSION,
            "--json",
        ],
    )
    .status
    .success());

    // The orchestrator's own harness listing reads the file we wrote, through
    // the same reader every Hermes-shaped store goes through.
    let listed = json_of(&run(
        &root,
        &["jobs", "list", "--harness", "orchestrator", "--json"],
    ));
    let ids: Vec<&str> = listed["jobs"]
        .as_array()
        .unwrap()
        .iter()
        .map(|job| job["id"].as_str().unwrap())
        .collect();
    assert_eq!(
        ids,
        vec![
            format!("{SESSION}-release-watch"),
            format!("{SESSION}-toolu_wake_recheck")
        ]
    );
    assert_eq!(listed["jobs"][0]["harness"], "orchestrator");
    assert_eq!(listed["jobs"][0]["schedule"]["display"], "*/10 * * * *");
    assert_eq!(
        listed["jobs"][0]["payload"]["text"],
        "Check the release branch for new failures."
    );

    // Compiling them out did not consume them: the manifest still holds them,
    // and Claude Code's own listing is unchanged.
    let claude = json_of(&run(
        &root,
        &["jobs", "list", "--harness", "claude-code", "--json"],
    ));
    let claude_ids: Vec<&str> = claude["jobs"]
        .as_array()
        .unwrap()
        .iter()
        .map(|job| job["id"].as_str().unwrap())
        .collect();
    assert_eq!(claude_ids, vec!["release-watch", "toolu_wake_recheck"]);

    std::fs::remove_dir_all(root).ok();
}

#[test]
fn a_second_import_replaces_only_its_own_rows_and_leaves_the_rest_alone() {
    let root = temp_root("merge");
    std::fs::create_dir_all(root.join("cron")).unwrap();
    let foreign = "[\n  {\n    \"id\": \"digest-15m\",\n    \"schedule\": {\n      \"kind\": \"interval\",\n      \"minutes\": 15\n    },\n    \"prompt\": \"digest\",\n    \"enabled\": true\n  }\n]\n";
    std::fs::write(root.join("cron/jobs.json"), foreign).unwrap();

    let args = [
        "orchestrator",
        "import",
        "--from",
        "claude-session",
        SESSION,
        "--json",
    ];
    let first = json_of(&run(&root, &args));
    assert_eq!(first["added"].as_array().unwrap().len(), 2);
    assert_eq!(first["untouched"], 1);

    let second = json_of(&run(&root, &args));
    assert!(second["added"].as_array().unwrap().is_empty());
    assert_eq!(second["replaced"].as_array().unwrap().len(), 2);
    assert_eq!(second["untouched"], 1);

    let jobs: Vec<serde_json::Value> =
        serde_json::from_str(&std::fs::read_to_string(root.join("cron/jobs.json")).unwrap())
            .unwrap();
    assert_eq!(jobs.len(), 3, "a re-import must not duplicate its own jobs");
    assert_eq!(jobs[0]["id"], "digest-15m");
    assert_eq!(jobs[0]["prompt"], "digest");
    assert_eq!(jobs[0]["schedule"]["minutes"], 15);

    std::fs::remove_dir_all(root).ok();
}

#[test]
fn an_unknown_source_and_an_unknown_session_are_refused_by_name() {
    let root = temp_root("refusals");

    let bad_kind = run(
        &root,
        &["orchestrator", "import", "--from", "codex-session", "abc"],
    );
    assert!(!bad_kind.status.success());
    let stderr = String::from_utf8_lossy(&bad_kind.stderr);
    assert!(stderr.contains("codex-session"), "{stderr}");
    assert!(stderr.contains("claude-session"), "{stderr}");

    let missing = run(
        &root,
        &[
            "orchestrator",
            "import",
            "--from",
            "claude-session",
            "00000000-0000-0000-0000-000000000000",
        ],
    );
    assert!(!missing.status.success());
    let stderr = String::from_utf8_lossy(&missing.stderr);
    assert!(
        stderr.contains("00000000-0000-0000-0000-000000000000"),
        "{stderr}"
    );
    assert!(
        !root.join("cron/jobs.json").exists(),
        "a refusal writes nothing"
    );

    std::fs::remove_dir_all(root).ok();
}