pleiades-agent 2.0.0

Pleiades CLI - Next-generation terminal AI assistant
use std::fs;

use assert_cmd::Command;
use predicates::prelude::*;

fn command(home: &std::path::Path) -> Command {
    let mut command = Command::cargo_bin("pleiades").unwrap();
    command
        .current_dir(home)
        .env("HOME", home)
        .env("XDG_CONFIG_HOME", home.join("config"));
    command
}

#[test]
fn top_level_and_workflow_help_snapshots() {
    let home = tempfile::tempdir().unwrap();
    let output = command(home.path()).arg("--help").output().unwrap();
    assert!(output.status.success());
    let text = String::from_utf8(output.stdout)
        .unwrap()
        .replace("pleiades.exe", "pleiades");
    assert!(text.contains("Pleiades is a terminal AI assistant"));
    assert!(text.contains("workflow"));
    assert!(text.contains("git"));
    assert!(text.contains("setup"));
    assert!(text.contains("doctor"));

    let output = command(home.path())
        .args(["workflow", "--help"])
        .output()
        .unwrap();
    let text = String::from_utf8(output.stdout)
        .unwrap()
        .replace("pleiades.exe", "pleiades");
    insta::assert_snapshot!(text, @r###"
Manage and run workflows

Usage: pleiades workflow [OPTIONS] <COMMAND>

Commands:
  list      List available workflow definitions
  run       Run a workflow
  show      Show a workflow definition
  validate  Validate a workflow definition
  create    Create a starter workflow in .pleiades/workflows
  help      Print this message or the help of the given subcommand(s)

Options:
  -m, --model <MODEL>                      Model to use
  -P, --provider <PROVIDER>                Provider to use
      --permission-mode <PERMISSION_MODE>  Agent mode: plan, agent, or unrestricted
  -v, --verbose                            Verbose output
  -h, --help                               Print help
"###);
}

#[test]
fn guided_api_setup_and_doctor_use_environment_reference() {
    let home = tempfile::tempdir().unwrap();
    command(home.path())
        .args(["setup", "--auth", "api-key"])
        .assert()
        .success()
        .stdout(predicate::str::contains("usage-based OpenAI API access"));

    let output = command(home.path())
        .args(["config", "path"])
        .output()
        .unwrap();
    assert!(output.status.success());
    let paths = String::from_utf8(output.stdout).unwrap();
    let config_path = paths
        .lines()
        .find_map(|line| {
            line.strip_prefix("Global: ")
                .and_then(|value| value.rsplit_once(" (").map(|(path, _)| path))
        })
        .expect("global config path");
    let config = fs::read_to_string(config_path).unwrap();
    assert!(config.contains("${OPENAI_API_KEY}"));
    assert!(!config.contains("sk-test"));

    command(home.path())
        .env("OPENAI_API_KEY", "sk-test")
        .arg("doctor")
        .assert()
        .success()
        .stdout(predicate::str::contains("API credential is available"))
        .stdout(predicate::str::contains("sk-test").not());
}

#[test]
fn workflow_create_validate_list_and_run() {
    let workspace = tempfile::tempdir().unwrap();
    command(workspace.path())
        .current_dir(workspace.path())
        .args([
            "workflow",
            "create",
            "smoke",
            "--description",
            "integration test",
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("Created"));
    command(workspace.path())
        .current_dir(workspace.path())
        .args(["workflow", "validate", "smoke"])
        .assert()
        .success()
        .stdout(predicate::str::contains("is valid"));
    command(workspace.path())
        .current_dir(workspace.path())
        .args(["workflow", "list"])
        .assert()
        .success()
        .stdout(predicate::str::contains("smoke"));
    command(workspace.path())
        .current_dir(workspace.path())
        .args(["workflow", "run", "smoke", "--var", "name=CLI"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Hello, CLI!"));
}

#[test]
fn prompt_render_and_provider_override_parse() {
    let home = tempfile::tempdir().unwrap();
    let output = command(home.path())
        .args([
            "--provider",
            "openai",
            "prompt",
            "render",
            "commit-message",
            "--var",
            "diff=example",
        ])
        .output()
        .unwrap();
    assert!(output.status.success());
    let text = String::from_utf8(output.stdout).unwrap();
    insta::assert_snapshot!(text, @r###"
Write a git commit message for the staged changes below.
Use the Conventional Commits format (type: subject). Keep the subject under 72 chars.
Add a short body only when it adds real context.

Diff:
example
"###);
}

#[test]
fn tool_execution_reads_a_file() {
    let workspace = tempfile::tempdir().unwrap();
    let file = workspace.path().join("sample.txt");
    fs::write(&file, "integration-content").unwrap();
    let input = serde_json::json!({ "path": file }).to_string();
    command(workspace.path())
        .current_dir(workspace.path())
        .args(["tool", "call", "read", &input])
        .assert()
        .success()
        .stdout(predicate::str::contains("integration-content"));
}

#[test]
fn repl_can_exit_cleanly() {
    let home = tempfile::tempdir().unwrap();
    command(home.path())
        .arg("repl")
        .write_stdin("/exit\n")
        .assert()
        .success();
}

#[test]
fn chat_command_can_exit_cleanly() {
    let home = tempfile::tempdir().unwrap();
    command(home.path())
        .env("OPENAI_API_KEY", "test-key")
        .write_stdin("/exit\n")
        .args(["chat", "--provider", "openai"])
        .assert()
        .success()
        .stdout(predicate::str::contains("P L E I A D E S"))
        .stdout(predicate::str::contains("workspace"));
}

#[test]
fn one_shot_prompt_uses_the_engine() {
    let temp = tempfile::tempdir().unwrap();
    let mut command = Command::cargo_bin("pleiades").unwrap();
    command
        .current_dir(temp.path())
        .env("XDG_CONFIG_HOME", temp.path().join("config"))
        .env_remove("OPENAI_API_KEY")
        .arg("hello");

    command
        .assert()
        .failure()
        .stderr(predicate::str::contains("Provider 'openai' not found"))
        .stderr(predicate::str::contains("Milestone 5").not());
}