Skip to main content

ito_core/harness/
claude_code.rs

1use super::streaming_cli::CliHarness;
2use super::types::{HarnessName, HarnessRunConfig};
3
4/// Runs the `claude` CLI in non-interactive print mode (`claude -p`).
5///
6/// Selected via `ito ralph --harness claude`; requires the Claude Code CLI on PATH.
7///
8/// # Examples
9///
10/// ```
11/// use ito_core::harness::{ClaudeCodeHarness, Harness, HarnessName};
12///
13/// let h = ClaudeCodeHarness;
14/// assert_eq!(h.name(), HarnessName::Claude);
15/// assert!(h.streams_output());
16/// ```
17#[derive(Debug, Default)]
18pub struct ClaudeCodeHarness;
19
20impl CliHarness for ClaudeCodeHarness {
21    fn harness_name(&self) -> HarnessName {
22        HarnessName::Claude
23    }
24
25    fn binary(&self) -> &str {
26        "claude"
27    }
28
29    fn build_args(&self, config: &HarnessRunConfig) -> Vec<String> {
30        let mut args = Vec::new();
31        if let Some(model) = config.model.as_deref() {
32            args.push("--model".to_string());
33            args.push(model.to_string());
34        }
35        if config.allow_all {
36            args.push("--dangerously-skip-permissions".to_string());
37        }
38        args.push("-p".to_string());
39        args.push(config.prompt.clone());
40        args
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use std::collections::BTreeMap;
48
49    enum Allow {
50        All,
51        None,
52    }
53
54    fn config(allow: Allow, model: Option<&str>) -> HarnessRunConfig {
55        let allow_all = match allow {
56            Allow::All => true,
57            Allow::None => false,
58        };
59        HarnessRunConfig {
60            prompt: "do stuff".to_string(),
61            model: model.map(String::from),
62            cwd: std::env::temp_dir(),
63            env: BTreeMap::new(),
64            interactive: false,
65            allow_all,
66            inactivity_timeout: None,
67        }
68    }
69
70    #[test]
71    fn harness_name_is_claude() {
72        let harness = ClaudeCodeHarness;
73        assert_eq!(harness.harness_name(), HarnessName::Claude);
74    }
75
76    #[test]
77    fn binary_is_claude() {
78        let harness = ClaudeCodeHarness;
79        assert_eq!(harness.binary(), "claude");
80    }
81
82    #[test]
83    fn build_args_with_allow_all() {
84        let harness = ClaudeCodeHarness;
85        let cfg = config(Allow::All, Some("sonnet"));
86        let args = harness.build_args(&cfg);
87        assert_eq!(
88            args,
89            vec![
90                "--model",
91                "sonnet",
92                "--dangerously-skip-permissions",
93                "-p",
94                "do stuff"
95            ]
96        );
97    }
98
99    #[test]
100    fn build_args_without_allow_all() {
101        let harness = ClaudeCodeHarness;
102        let cfg = config(Allow::None, Some("sonnet"));
103        let args = harness.build_args(&cfg);
104        assert_eq!(args, vec!["--model", "sonnet", "-p", "do stuff"]);
105    }
106
107    #[test]
108    fn build_args_without_model() {
109        let harness = ClaudeCodeHarness;
110        let cfg = config(Allow::None, None);
111        let args = harness.build_args(&cfg);
112        assert_eq!(args, vec!["-p", "do stuff"]);
113    }
114}