1use crate::contracts::{
18 GitRevertMode, Runner, RunnerApprovalMode, RunnerCliOptionsPatch, RunnerOutputFormat,
19 RunnerPlanMode, RunnerSandboxMode, RunnerVerbosity, UnsupportedOptionPolicy,
20};
21use anyhow::{Result, anyhow, bail};
22
23use super::args::RunnerCliArgs;
24
25pub fn parse_runner(value: &str) -> Result<Runner> {
27 let normalized = value.trim().to_lowercase();
28 match normalized.as_str() {
29 "codex" => Ok(Runner::Codex),
30 "opencode" => Ok(Runner::Opencode),
31 "gemini" => Ok(Runner::Gemini),
32 "claude" => Ok(Runner::Claude),
33 "cursor" => Ok(Runner::Cursor),
34 "kimi" => Ok(Runner::Kimi),
35 "pi" => Ok(Runner::Pi),
36 _ => bail!(
37 "Invalid runner: --runner must be 'codex', 'opencode', 'gemini', 'claude', 'cursor', 'kimi', or 'pi' (got: {}). Set a supported runner in .ralph/config.jsonc or via the --runner flag.",
38 value.trim()
39 ),
40 }
41}
42
43pub fn parse_git_revert_mode(value: &str) -> Result<GitRevertMode> {
45 value.parse().map_err(|err: &str| anyhow!(err))
46}
47
48pub(crate) fn parse_runner_cli_patch(args: &RunnerCliArgs) -> Result<RunnerCliOptionsPatch> {
50 let output_format = match args.output_format.as_deref() {
51 Some(value) => Some(
52 value
53 .parse::<RunnerOutputFormat>()
54 .map_err(|err| anyhow!(err))?,
55 ),
56 None => None,
57 };
58 let verbosity = match args.verbosity.as_deref() {
59 Some(value) => Some(
60 value
61 .parse::<RunnerVerbosity>()
62 .map_err(|err| anyhow!(err))?,
63 ),
64 None => None,
65 };
66 let approval_mode = match args.approval_mode.as_deref() {
67 Some(value) => Some(
68 value
69 .parse::<RunnerApprovalMode>()
70 .map_err(|err| anyhow!(err))?,
71 ),
72 None => None,
73 };
74 let sandbox = match args.sandbox.as_deref() {
75 Some(value) => Some(
76 value
77 .parse::<RunnerSandboxMode>()
78 .map_err(|err| anyhow!(err))?,
79 ),
80 None => None,
81 };
82 let plan_mode = match args.plan_mode.as_deref() {
83 Some(value) => Some(
84 value
85 .parse::<RunnerPlanMode>()
86 .map_err(|err| anyhow!(err))?,
87 ),
88 None => None,
89 };
90 let unsupported_option_policy = match args.unsupported_option_policy.as_deref() {
91 Some(value) => Some(
92 value
93 .parse::<UnsupportedOptionPolicy>()
94 .map_err(|err| anyhow!(err))?,
95 ),
96 None => None,
97 };
98
99 Ok(RunnerCliOptionsPatch {
100 output_format,
101 verbosity,
102 approval_mode,
103 sandbox,
104 plan_mode,
105 unsupported_option_policy,
106 })
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn parse_runner_accepts_valid_runners() {
115 assert!(matches!(parse_runner("codex"), Ok(Runner::Codex)));
116 assert!(matches!(parse_runner("opencode"), Ok(Runner::Opencode)));
117 assert!(matches!(parse_runner("gemini"), Ok(Runner::Gemini)));
118 assert!(matches!(parse_runner("claude"), Ok(Runner::Claude)));
119 assert!(matches!(parse_runner("cursor"), Ok(Runner::Cursor)));
120 assert!(matches!(parse_runner("kimi"), Ok(Runner::Kimi)));
121 assert!(matches!(parse_runner("pi"), Ok(Runner::Pi)));
122 assert!(matches!(parse_runner("CODEX"), Ok(Runner::Codex)));
123 }
124
125 #[test]
126 fn parse_runner_rejects_invalid_runners() {
127 assert!(parse_runner("invalid").is_err());
128 assert!(parse_runner("").is_err());
129 }
130}