supercode-cli 0.4.17

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! ORC-13 CLI acceptance: `supercode jobs|profiles|sessions <verb> --harness
//! orchestrator` against the REAL `supercode-orchestrator` package, on its
//! COLD path (no daemon running).
//!
//! The binary is driven exactly as a user drives it — the home comes from
//! `SUPERCODE_ORCHESTRATOR_HOME`, the package entry from the documented
//! override `SUPERCODE_ORCHESTRATOR_ENTRY` — and nothing is injected past the
//! product's own door. Every assertion is about the FOLDER the package wrote
//! and the row supercode's own readers report afterwards, never about the
//! request that was sent.
//!
//! Offline: node runs the package, no worker is started, no adapter connects,
//! and no model is called.

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

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

/// The package entry in this checkout, resolved the way `orchestrator start`
/// resolves it — from the workspace the test binary was compiled in.
fn entry() -> PathBuf {
    let workspace = Path::new(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(2)
        .expect("workspace root");
    workspace.join("sdk/orchestrator/bin/orchestrator.mjs")
}

/// A scratch root per TEST: these run as threads in one process, so the label
/// is what keeps two of them from sharing a home when the clock does not tick
/// between their starts.
fn scratch(label: &str) -> PathBuf {
    let root = std::env::temp_dir().join(format!(
        "supercode-orc13-cli-{label}-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&root).unwrap();
    root
}

/// A minimal orchestrator home: the root folder IS the `default` profile
/// (`docs/ORCHESTRATOR-IR.md` §6).
fn seed(root: &Path) -> PathBuf {
    let home = root.join("orchestrator");
    std::fs::create_dir_all(home.join("profiles/coder")).unwrap();
    std::fs::write(
        home.join("config.yaml"),
        "worker:\n  harness: hermes\nexpiry:\n  idle_minutes: 30\n",
    )
    .unwrap();
    std::fs::write(home.join("AGENTS.md"), "# Assistant\n").unwrap();
    std::fs::write(home.join("CLAUDE.md"), "@AGENTS.md\n").unwrap();
    std::fs::write(
        home.join("profiles/coder/config.yaml"),
        "worker:\n  harness: hermes\n",
    )
    .unwrap();
    home
}

fn run(home: &Path, args: &[&str]) -> Output {
    Command::new(bin())
        .env("SUPERCODE_ORCHESTRATOR_HOME", home)
        .env("SUPERCODE_ORCHESTRATOR_ENTRY", entry())
        .args(args)
        .output()
        .expect("supercode binary runs")
}

fn stdout_of(output: &Output) -> String {
    assert!(
        output.status.success(),
        "command failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8_lossy(&output.stdout).into_owned()
}

fn json_of(output: &Output) -> serde_json::Value {
    serde_json::from_str(&stdout_of(output)).expect("json output")
}

/// Skip when node is not on PATH: the orchestrator's writer IS a Node
/// package, so without it there is nothing to drive — and a silently green
/// test would be worse than a skipped one.
fn have_node() -> bool {
    Command::new("node")
        .arg("--version")
        .output()
        .is_ok_and(|output| output.status.success())
}

#[test]
fn jobs_create_writes_through_the_packages_own_writer_and_the_readers_agree() {
    if !have_node() {
        eprintln!("skipped: node is not on PATH");
        return;
    }
    let root = scratch("jobs");
    let home = seed(&root);

    let created = json_of(&run(
        &home,
        &[
            "jobs",
            "create",
            "--harness",
            "orchestrator",
            "--every",
            "30m",
            "--message",
            "check the repo",
            "--name",
            "repo check",
            "--deliver",
            "local",
            "--json",
        ],
    ));
    // `ran` narrates the exact door that was driven, and says which one.
    let ran = created["ran"].as_str().unwrap();
    assert!(ran.contains("jobs.create"), "{ran}");
    assert!(ran.ends_with("[cold]"), "{ran}");
    assert!(ran.contains("orchestrator.mjs"), "{ran}");

    // The row is the FOLDER's, re-read through the ORC-7 loader.
    let id = created["id"].as_str().unwrap().to_string();
    assert_eq!(created["job"]["harness"], "orchestrator");
    assert_eq!(created["job"]["payload"]["text"], "check the repo");
    assert_eq!(created["job"]["schedule"]["display"], "every 30 min");
    assert_eq!(created["job"]["deliver"]["target"], "local");
    assert_eq!(created["job"]["enabled"], true);

    // and `jobs list` — a different reader, the same file — shows it.
    let listed = json_of(&run(
        &home,
        &["jobs", "list", "--harness", "orchestrator", "--json"],
    ));
    let jobs = listed["jobs"].as_array().unwrap();
    assert_eq!(jobs.len(), 1);
    assert_eq!(jobs[0]["id"], id.as_str());
    assert_eq!(
        listed["sources"][0]["path"],
        home.join("cron/jobs.json").to_string_lossy().as_ref()
    );

    // pause / resume / run each move exactly their own field.
    assert_eq!(
        json_of(&run(
            &home,
            &["jobs", "pause", "--harness", "orchestrator", &id, "--json"]
        ))["job"]["enabled"],
        false
    );
    assert_eq!(
        json_of(&run(
            &home,
            &["jobs", "resume", "--harness", "orchestrator", &id, "--json"]
        ))["job"]["enabled"],
        true
    );

    // delete removes it, and the reader agrees.
    let deleted = json_of(&run(
        &home,
        &["jobs", "delete", "--harness", "orchestrator", &id, "--json"],
    ));
    assert_eq!(deleted["deleted"], true);
    let after = json_of(&run(
        &home,
        &["jobs", "list", "--harness", "orchestrator", "--json"],
    ));
    assert!(after["jobs"].as_array().unwrap().is_empty());

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

#[test]
fn a_verb_the_reducer_refuses_surfaces_its_reason_and_writes_nothing() {
    if !have_node() {
        eprintln!("skipped: node is not on PATH");
        return;
    }
    let root = scratch("refuse");
    let home = seed(&root);

    let output = run(
        &home,
        &[
            "jobs",
            "delete",
            "--harness",
            "orchestrator",
            "job_does_not_exist",
            "--json",
        ],
    );
    assert!(!output.status.success(), "a refusal must not exit 0");
    let stderr = String::from_utf8_lossy(&output.stderr);
    // The PACKAGE's own sentence, not a supercode paraphrase.
    assert!(
        stderr.contains("jobs_delete: no job job_does_not_exist"),
        "{stderr}"
    );
    // and nothing was written: there is still no jobs file at all.
    assert!(
        !home.join("cron/jobs.json").exists(),
        "a refusal writes nothing"
    );

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

#[test]
fn profiles_create_and_delete_go_through_the_package_and_the_trash() {
    if !have_node() {
        eprintln!("skipped: node is not on PATH");
        return;
    }
    let root = scratch("profiles");
    let home = seed(&root);

    let created = json_of(&run(
        &home,
        &[
            "profiles",
            "create",
            "--harness",
            "orchestrator",
            "ops",
            "--json",
        ],
    ));
    assert!(created["ran"].as_str().unwrap().contains("profiles.create"));
    assert_eq!(created["profile"]["kind"], "orchestrator_profile");
    assert_eq!(
        created["profile"]["home"],
        home.join("profiles/ops").to_string_lossy().as_ref()
    );
    // The package's own writer emitted the config, not supercode.
    assert!(home.join("profiles/ops/config.yaml").is_file());

    let listed = json_of(&run(
        &home,
        &["profiles", "list", "--harness", "orchestrator", "--json"],
    ));
    let names: Vec<&str> = listed["profiles"]
        .as_array()
        .unwrap()
        .iter()
        .map(|row| row["name"].as_str().unwrap())
        .collect();
    assert_eq!(names, vec!["default", "coder", "ops"]);

    let deleted = json_of(&run(
        &home,
        &[
            "--yes",
            "profiles",
            "delete",
            "--harness",
            "orchestrator",
            "ops",
            "--json",
        ],
    ));
    assert_eq!(deleted["deleted"], true);
    assert!(!home.join("profiles/ops").exists());
    // Nothing is unlinked: the home was RENAMED into the trash, and the trash
    // is not a profile.
    let trash: Vec<PathBuf> = std::fs::read_dir(home.join("profiles/.trash"))
        .expect("the trash exists")
        .flatten()
        .map(|entry| entry.path())
        .collect();
    assert_eq!(trash.len(), 1, "{trash:?}");
    assert!(trash[0].join("config.yaml").is_file());
    let after = json_of(&run(
        &home,
        &["profiles", "list", "--harness", "orchestrator", "--json"],
    ));
    let names: Vec<&str> = after["profiles"]
        .as_array()
        .unwrap()
        .iter()
        .map(|row| row["name"].as_str().unwrap())
        .collect();
    assert_eq!(names, vec!["default", "coder"]);

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

#[test]
fn sessions_reset_names_the_binding_by_its_surface_and_refuses_a_session_id() {
    if !have_node() {
        eprintln!("skipped: node is not on PATH");
        return;
    }
    let root = scratch("sessions");
    let home = seed(&root);

    // No binding on that surface: the package refuses BY NAME rather than
    // reporting an empty success.
    let output = run(
        &home,
        &[
            "sessions",
            "reset",
            "--harness",
            "orchestrator",
            "--surface",
            "loopback|group|coder-room||",
            "--json",
        ],
    );
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("no live conversation on loopback|group|coder-room||"),
        "{stderr}"
    );

    // A conversation is a BINDING, so a runtime id is the wrong selector and
    // the refusal says which one is right.
    let output = run(
        &home,
        &[
            "sessions",
            "reset",
            "--harness",
            "orchestrator",
            "--session",
            "some-runtime",
            "--json",
        ],
    );
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("--surface"), "{stderr}");

    // Archive and delete do not exist in the model at all.
    let output = run(
        &home,
        &[
            "sessions",
            "archive",
            "x",
            "--harness",
            "orchestrator",
            "--json",
        ],
    );
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("a binding is never archived or deleted"),
        "{stderr}"
    );

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