supercode-cli 0.4.19

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! ORCH-9 dev/01 acceptance at the CLI: `supercode approvals list` speaks the
//! same door `harness.v1.approvals.list` does, and refuses by name rather than
//! answering an empty list for a harness whose runtime cannot carry a
//! protocol request.
//!
//! Fully offline, and deliberately without a live harness: an approval request
//! at the pinned harness versions exists only while the turn it blocks is
//! open, inside the process holding that runtime connection. A fresh CLI
//! process holds none, so `--json` is an empty array — the honest answer, and
//! the one the human table explains instead of implying nothing is
//! outstanding anywhere. The row shape itself is proven over a live mock
//! runtime in `harness_service`'s
//! `a_live_permission_request_lists_until_it_is_answered`.

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

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

fn scratch(tag: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!(
        "supercode-orch9-{tag}-{}-{}",
        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(args: &[&str]) -> Output {
    let home = scratch("home");
    let output = Command::new(bin())
        .env("SUPERCODE_HOME", home.join("sessions"))
        .env("HOME", &home)
        .env("NO_COLOR", "1")
        .env_remove("OPENROUTER_API_KEY")
        .args(args)
        .stdin(std::process::Stdio::null())
        .output()
        .expect("supercode binary runs");
    let _ = std::fs::remove_dir_all(&home);
    output
}

fn stdout(args: &[&str]) -> String {
    let output = run(args);
    assert!(
        output.status.success(),
        "`supercode {}` failed: {}",
        args.join(" "),
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8(output.stdout).unwrap()
}

/// `--json` is the RPC rows verbatim: a JSON array, empty when this process
/// holds no runtime connection and no subagent queue.
#[test]
fn approvals_list_json_is_the_rpc_row_array() {
    let rows: Vec<serde_json::Value> =
        serde_json::from_str(&stdout(&["approvals", "list", "--json"]))
            .expect("approvals list --json is a JSON array");
    assert!(rows.is_empty(), "{rows:#?}");

    // The filters parse and select against the same rows.
    let filtered: Vec<serde_json::Value> = serde_json::from_str(&stdout(&[
        "approvals",
        "list",
        "--harness",
        "hermes",
        "--session",
        "some-session",
        "--json",
    ]))
    .expect("a filtered listing is a JSON array");
    assert!(filtered.is_empty(), "{filtered:#?}");
}

/// The human table never implies "nothing is outstanding anywhere": it says
/// where a live request actually lives.
#[test]
fn approvals_list_table_names_where_a_live_request_lives() {
    let table = stdout(&["approvals", "list"]);
    assert!(table.contains("no approval request is waiting"), "{table}");
    assert!(table.contains("harness.v1.approvals.list"), "{table}");
}

/// The uniform-verb contract: Claude Code's stream-json print mode exposes no
/// permission-response primitive, so it is refused by name. An unknown id is
/// refused the same way.
#[test]
fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
    for harness in ["claude-code", "notaharness"] {
        let output = run(&["approvals", "list", "--harness", harness]);
        assert!(!output.status.success(), "{harness} should be refused");
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains(harness), "{stderr}");
        assert!(stderr.contains("unsupported action"), "{stderr}");
        // The refusal names the harnesses that DO have the door.
        assert!(stderr.contains("hermes"), "{stderr}");
    }
}

// ---- ORCH-20: `supercode approvals resolve <id> <decision>` ---------------

/// The decision vocabulary is the uniform one, and clap validates it before
/// any door is touched: an invented decision names the three that exist.
#[test]
fn approvals_resolve_takes_only_the_three_uniform_decisions() {
    let output = run(&["approvals", "resolve", "runtime-1/7", "maybe"]);
    assert!(!output.status.success(), "an invented decision is refused");
    let stderr = String::from_utf8_lossy(&output.stderr);
    for decision in ["allow-once", "allow-always", "deny"] {
        assert!(stderr.contains(decision), "{stderr}");
    }
}

/// A row id this process is not holding is reported as unknown WITH the
/// reason — a live request lives inside the process driving the runtime its
/// turn is blocked on. It is never answered into the void, and never a
/// silent success.
#[test]
fn approvals_resolve_refuses_a_row_this_process_is_not_holding() {
    for decision in ["allow-once", "allow-always", "deny"] {
        let output = run(&["approvals", "resolve", "runtime-1/7", decision]);
        assert!(
            !output.status.success(),
            "resolving a row nobody holds must fail"
        );
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(stderr.contains("runtime-1/7"), "{stderr}");
        assert!(stderr.contains("only inside the process"), "{stderr}");
    }
}

/// supercode's own queued subagent row is addressable but is the parent's
/// audit copy, not an answerable door — the refusal says which door does
/// answer it.
#[test]
fn approvals_resolve_names_the_door_for_a_queued_subagent_row() {
    let output = run(&[
        "approvals",
        "resolve",
        "supercode/subagent/child-1/100/0",
        "deny",
    ]);
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("queued subagent record"), "{stderr}");
    assert!(stderr.contains("supercode runtime"), "{stderr}");
}

/// The verb is on the CLI's own help, beside `list`.
#[test]
fn approvals_help_advertises_both_verbs() {
    let help = stdout(&["approvals", "--help"]);
    assert!(help.contains("list"), "{help}");
    assert!(help.contains("resolve"), "{help}");
}