harn-cli 0.8.55

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use super::harnpack::HarnpackRunOptions;
use super::{
    default_run_workspace_root, execute_explain_cost, execute_run,
    execute_run_with_harnpack_and_sandbox_options, run_sandbox_attestation, split_eval_header,
    CliLlmMockMode, RunProfileOptions, RunSandboxOptions, StdoutPassthroughGuard,
};
use std::collections::HashSet;
use std::path::Path;

#[test]
fn split_eval_header_no_imports_returns_full_body() {
    let (header, body) = split_eval_header("log(1 + 2)");
    assert_eq!(header, "");
    assert_eq!(body, "log(1 + 2)");
}

#[test]
fn split_eval_header_lifts_leading_imports() {
    let code = "import \"./lib\"\nimport { x } from \"std/math\"\nlog(x)";
    let (header, body) = split_eval_header(code);
    assert_eq!(header, "import \"./lib\"\nimport { x } from \"std/math\"");
    assert_eq!(body, "log(x)");
}

#[test]
fn split_eval_header_keeps_pub_import_and_comments_in_header() {
    let code = "// header comment\npub import { y } from \"./lib\"\n\nfoo()";
    let (header, body) = split_eval_header(code);
    assert_eq!(
        header,
        "// header comment\npub import { y } from \"./lib\"\n"
    );
    assert_eq!(body, "foo()");
}

#[test]
fn split_eval_header_does_not_lift_imports_after_other_statements() {
    let code = "let a = 1\nimport \"./lib\"";
    let (header, body) = split_eval_header(code);
    assert_eq!(header, "");
    assert_eq!(body, "let a = 1\nimport \"./lib\"");
}

#[test]
fn cli_llm_mock_roundtrips_logprobs() {
    let mock = harn_vm::llm::parse_llm_mock_value(&serde_json::json!({
        "text": "visible",
        "logprobs": [{"token": "visible", "logprob": 0.0}]
    }))
    .expect("parse mock");
    assert_eq!(mock.logprobs.len(), 1);

    let line = harn_vm::llm::serialize_llm_mock(mock).expect("serialize mock");
    let value: serde_json::Value = serde_json::from_str(&line).expect("json line");
    assert_eq!(value["logprobs"][0]["token"].as_str(), Some("visible"));

    let reparsed = harn_vm::llm::parse_llm_mock_value(&value).expect("reparse mock");
    assert_eq!(reparsed.logprobs.len(), 1);
    assert_eq!(reparsed.logprobs[0]["logprob"].as_f64(), Some(0.0));
}

#[test]
fn stdout_passthrough_guard_restores_previous_state() {
    let original = harn_vm::set_stdout_passthrough(false);
    {
        let _guard = StdoutPassthroughGuard::enable();
        assert!(harn_vm::set_stdout_passthrough(true));
    }
    assert!(!harn_vm::set_stdout_passthrough(original));
}

#[test]
fn execute_explain_cost_does_not_execute_script() {
    let temp = tempfile::TempDir::new().expect("temp dir");
    let script = temp.path().join("main.harn");
    std::fs::write(
        &script,
        r#"
pipeline main() {
  write_file("executed.txt", "bad")
  llm_call("hello", nil, {provider: "mock", model: "mock"})
}
"#,
    )
    .expect("write script");

    let outcome = execute_explain_cost(&script.to_string_lossy());

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert!(outcome.stdout.contains("LLM cost estimate"));
    assert!(
        !temp.path().join("executed.txt").exists(),
        "--explain-cost must not execute pipeline side effects"
    );
}

#[test]
fn default_run_workspace_root_prefers_manifest_root_then_cwd() {
    let project = tempfile::TempDir::new().expect("project");
    let source_parent = project.path().join("scripts");
    let cwd = std::env::current_dir().expect("cwd");

    assert_eq!(
        default_run_workspace_root(Some(project.path()), &source_parent),
        project.path()
    );
    assert_eq!(default_run_workspace_root(None, Path::new("scripts")), cwd);
}

#[test]
fn run_sandbox_attestation_reports_effective_policy() {
    harn_vm::reset_thread_local_state();
    let policy = harn_vm::orchestration::CapabilityPolicy {
        workspace_roots: vec!["/tmp/workspace".to_string()],
        sandbox_profile: harn_vm::orchestration::SandboxProfile::OsHardened,
        ..harn_vm::orchestration::CapabilityPolicy::default()
    };
    harn_vm::orchestration::push_execution_policy(policy);

    let metadata = run_sandbox_attestation(&RunSandboxOptions::disabled());

    assert_eq!(metadata["run_default_enabled"], false);
    assert_eq!(metadata["active"], true);
    assert_eq!(metadata["workspace_roots"][0], "/tmp/workspace");
    assert_eq!(metadata["profile"], "os_hardened");
    assert_eq!(metadata["egress"], "host_policy");
    harn_vm::reset_thread_local_state();
}

#[tokio::test]
async fn execute_run_default_sandbox_reports_worktree_profile() {
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let script = temp.path().join("main.harn");
    std::fs::write(
        &script,
        r"
pipeline main() {
  __io_println(sandbox_active_profile())
}
",
    )
    .expect("write script");

    let outcome = execute_run(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout.trim(), "worktree");
    harn_vm::reset_thread_local_state();
}

#[tokio::test]
async fn execute_run_default_sandbox_blocks_outside_workspace_read() {
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let project = temp.path().join("project");
    let outside = temp.path().join("outside.txt");
    std::fs::create_dir(&project).expect("create project");
    std::fs::write(project.join("harn.toml"), "").expect("write manifest");
    std::fs::write(&outside, "secret").expect("write outside");
    let script = project.join("main.harn");
    let outside_literal = outside.to_string_lossy().replace('\\', "\\\\");
    std::fs::write(
        &script,
        format!(
            r#"
pipeline main() {{
  __io_println(sandbox_active_profile())
  let _ = read_file("{outside_literal}")
}}
"#
        ),
    )
    .expect("write script");

    let outcome = execute_run(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 1, "stdout:\n{}", outcome.stdout);
    assert!(
        outcome.stderr.contains("sandbox violation"),
        "stderr:\n{}",
        outcome.stderr
    );
    harn_vm::reset_thread_local_state();
}

#[tokio::test]
async fn execute_run_no_sandbox_allows_outside_workspace_read() {
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let project = temp.path().join("project");
    let outside = temp.path().join("outside.txt");
    std::fs::create_dir(&project).expect("create project");
    std::fs::write(&outside, "secret").expect("write outside");
    let script = project.join("main.harn");
    let outside_literal = outside.to_string_lossy().replace('\\', "\\\\");
    std::fs::write(
        &script,
        format!(
            r#"
pipeline main() {{
  __io_println(sandbox_active_profile())
  __io_println(read_file("{outside_literal}"))
}}
"#
        ),
    )
    .expect("write script");

    let outcome = execute_run_with_harnpack_and_sandbox_options(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
        RunSandboxOptions::disabled(),
        HarnpackRunOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout.trim(), "unrestricted\nsecret");
    assert!(outcome.stderr.contains("--no-sandbox"));
    harn_vm::reset_thread_local_state();
}

#[tokio::test]
async fn execute_run_denies_network_by_default() {
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let script = temp.path().join("main.harn");
    std::fs::write(
        &script,
        r#"
pipeline main() {
  let _ = http_get("https://example.com/")
}
"#,
    )
    .expect("write script");

    let outcome = execute_run(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 1, "stdout:\n{}", outcome.stdout);
    assert!(
        outcome.stderr.contains("exceeds network ceiling"),
        "stderr:\n{}",
        outcome.stderr
    );
    harn_vm::reset_thread_local_state();
}

#[cfg(feature = "hostlib")]
#[tokio::test]
async fn execute_run_installs_hostlib_gate() {
    let temp = tempfile::NamedTempFile::new().expect("temp file");
    std::fs::write(
        temp.path(),
        r#"
pipeline main() {
  let _ = hostlib_enable("tools:deterministic")
  __io_println("enabled")
}
"#,
    )
    .expect("write script");

    let outcome = execute_run(
        &temp.path().to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout.trim(), "enabled");
}

#[cfg(all(feature = "hostlib", unix))]
#[tokio::test]
async fn execute_run_can_read_hostlib_command_artifacts() {
    let temp = tempfile::NamedTempFile::new().expect("temp file");
    std::fs::write(
        temp.path(),
        r#"
pipeline main() {
  let _ = hostlib_enable("tools:deterministic")
  let result = hostlib_tools_run_command({
argv: ["sh", "-c", "i=0; while [ $i -lt 2000 ]; do printf x; i=$((i+1)); done"],
capture: {max_inline_bytes: 8},
timeout_ms: 5000,
  })
  __io_println(starts_with(result.command_id, "cmd_"))
  __io_println(len(result.stdout))
  __io_println(result.byte_count)
  let window = hostlib_tools_read_command_output({
command_id: result.command_id,
offset: 1990,
length: 20,
  })
  __io_println(len(window.content))
  __io_println(window.eof)
}
"#,
    )
    .expect("write script");

    let outcome = execute_run_with_harnpack_and_sandbox_options(
        &temp.path().to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
        RunSandboxOptions::disabled(),
        HarnpackRunOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout.trim(), "true\n8\n2000\n10\ntrue");
}