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.
//! ORCH-10 acceptance: `supercode profiles list|show` against the committed
//! Hermes and OpenClaw fixture homes, through the real binary.
//!
//! The rows are the `harness.v1.profiles.list` rows verbatim, so this test
//! also pins the CLI's `--json` contract to the RPC's.

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

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

/// The harness crate's fixture homes: `<workspace>/crates/harness/tests/fixtures`.
fn fixtures() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../harness/tests/fixtures")
        .canonicalize()
        .unwrap()
}

fn run(args: &[&str]) -> String {
    let fixtures = fixtures();
    let output = Command::new(bin())
        // HERMES_HOME names the home whose `state.db` supercode reads;
        // OPENCLAW_STATE_DIR names openclaw's state directory directly (the
        // same env semantics openclaw itself uses).
        .env("HERMES_HOME", fixtures.join("hermes_home"))
        .env("OPENCLAW_STATE_DIR", fixtures.join("openclaw_home"))
        .args(args)
        .output()
        .expect("supercode binary runs");
    assert!(
        output.status.success(),
        "`{}` failed: {}",
        args.join(" "),
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8(output.stdout).unwrap()
}

fn row<'a>(value: &'a serde_json::Value, harness: &str, name: &str) -> &'a serde_json::Value {
    value["profiles"]
        .as_array()
        .expect("profiles array")
        .iter()
        .find(|row| row["harness"] == harness && row["name"] == name)
        .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {value}"))
}

#[test]
fn profiles_list_json_carries_every_source_in_one_row_shape() {
    let stdout = run(&["profiles", "list", "--json"]);
    let value: serde_json::Value = serde_json::from_str(&stdout).unwrap();
    assert_eq!(value["schema"], "supercode.profiles.v1");

    // supercode's own presets are its profile analog: no home, no store.
    let preset = row(&value, "supercode", "supercode-default");
    assert_eq!(preset["kind"], "preset");
    assert_eq!(preset["default"], true);
    assert_eq!(preset["home"], serde_json::Value::Null);

    // Hermes: the implicit default plus the `profiles/coder` config home,
    // routed by `gateway.profile_routes` and partitioned in `state.db`.
    let default = row(&value, "hermes", "default");
    assert_eq!(default["kind"], "hermes_profile");
    assert_eq!(default["default"], true);
    assert_eq!(default["sessions"], 11);
    let coder = row(&value, "hermes", "coder");
    assert_eq!(coder["routes"], 1);
    assert_eq!(coder["sessions"], 1);
    assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
    assert!(Path::new(coder["home"].as_str().unwrap()).is_dir());

    // OpenClaw: agent homes plus `agents.entries` / `bindings[]`.
    let main = row(&value, "openclaw", "main");
    assert_eq!(main["kind"], "openclaw_agent");
    assert_eq!(main["default"], true);
    assert_eq!(main["sessions"], 4);
    let design = row(&value, "openclaw", "design");
    assert_eq!(design["routes"], 1);
    assert_eq!(design["sessions"], 0);
}

#[test]
fn profiles_list_filters_by_harness_and_prints_a_table() {
    let stdout = run(&["profiles", "list", "--harness", "openclaw"]);
    let header = stdout.lines().next().unwrap();
    for column in ["HARNESS", "NAME", "KIND", "DEFAULT", "ROUTES", "SESSIONS"] {
        assert!(header.contains(column), "missing `{column}` in `{header}`");
    }
    // Column widths are the printer's business; what this row asserts is that
    // the harness and the name are on the same line, in that order.
    let cells = |name: &str| {
        stdout.lines().any(|line| {
            line.starts_with("openclaw") && line.split_whitespace().nth(1) == Some(name)
        })
    };
    assert!(cells("design"), "{stdout}");
    assert!(cells("main"), "{stdout}");
    assert!(
        !stdout.contains("hermes"),
        "harness filter leaked: {stdout}"
    );
}

#[test]
fn profiles_show_reads_one_row() {
    let stdout = run(&["profiles", "show", "--harness", "hermes", "coder", "--json"]);
    let value: serde_json::Value = serde_json::from_str(&stdout).unwrap();
    assert_eq!(value["schema"], "supercode.profiles.v1");
    assert_eq!(value["profile"]["name"], "coder");
    assert_eq!(value["profile"]["kind"], "hermes_profile");
    assert_eq!(value["profile"]["routes"], 1);
}

/// A verb a harness lacks fails, never a silent empty table.
#[test]
fn unsupported_harness_is_refused() {
    let output = Command::new(bin())
        .args(["profiles", "list", "--harness", "claude-code"])
        .output()
        .unwrap();
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("has no profile concept"),
        "unexpected refusal: {stderr}"
    );
}