ralph/commands/runner/
list.rs1use anyhow::Result;
7use serde::Serialize;
8
9use crate::cli::runner::RunnerFormat;
10use crate::contracts::Runner;
11use crate::runner::default_model_for_runner;
12
13#[derive(Debug, Clone, Serialize)]
14pub struct RunnerInfo {
15 pub id: String,
16 pub name: String,
17 pub provider: String,
18 pub default_model: String,
19}
20
21fn get_all_runners() -> Vec<RunnerInfo> {
22 vec![
23 RunnerInfo {
24 id: "claude".into(),
25 name: "Anthropic Claude Code".into(),
26 provider: "Anthropic".into(),
27 default_model: default_model_for_runner(&Runner::Claude)
28 .as_str()
29 .to_string(),
30 },
31 RunnerInfo {
32 id: "codex".into(),
33 name: "OpenAI Codex CLI".into(),
34 provider: "OpenAI".into(),
35 default_model: default_model_for_runner(&Runner::Codex)
36 .as_str()
37 .to_string(),
38 },
39 RunnerInfo {
40 id: "opencode".into(),
41 name: "Opencode".into(),
42 provider: "Flexible".into(),
43 default_model: default_model_for_runner(&Runner::Opencode)
44 .as_str()
45 .to_string(),
46 },
47 RunnerInfo {
48 id: "gemini".into(),
49 name: "Google Gemini CLI".into(),
50 provider: "Google".into(),
51 default_model: default_model_for_runner(&Runner::Gemini)
52 .as_str()
53 .to_string(),
54 },
55 RunnerInfo {
56 id: "cursor".into(),
57 name: "Cursor Agent".into(),
58 provider: "Cursor".into(),
59 default_model: default_model_for_runner(&Runner::Cursor)
60 .as_str()
61 .to_string(),
62 },
63 RunnerInfo {
64 id: "kimi".into(),
65 name: "Kimi CLI".into(),
66 provider: "Moonshot AI".into(),
67 default_model: default_model_for_runner(&Runner::Kimi).as_str().to_string(),
68 },
69 RunnerInfo {
70 id: "pi".into(),
71 name: "Pi Coding Agent".into(),
72 provider: "Pi".into(),
73 default_model: default_model_for_runner(&Runner::Pi).as_str().to_string(),
74 },
75 ]
76}
77
78pub fn handle_list(format: RunnerFormat) -> Result<()> {
79 let runners = get_all_runners();
80
81 match format {
82 RunnerFormat::Text => {
83 println!("Available runners:\n");
84 for r in &runners {
85 println!(" {:12} {} (default: {})", r.id, r.name, r.default_model);
86 }
87 println!("\nUse 'ralph runner capabilities <id>' for details.");
88 }
89 RunnerFormat::Json => {
90 println!("{}", serde_json::to_string_pretty(&runners)?);
91 }
92 }
93
94 Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn get_all_runners_returns_all_built_ins() {
103 let runners = get_all_runners();
104 assert_eq!(runners.len(), 7);
105
106 let ids: Vec<_> = runners.iter().map(|r| r.id.as_str()).collect();
107 assert!(ids.contains(&"claude"));
108 assert!(ids.contains(&"codex"));
109 assert!(ids.contains(&"opencode"));
110 assert!(ids.contains(&"gemini"));
111 assert!(ids.contains(&"cursor"));
112 assert!(ids.contains(&"kimi"));
113 assert!(ids.contains(&"pi"));
114 }
115
116 #[test]
117 fn runner_info_has_required_fields() {
118 let runners = get_all_runners();
119 for r in &runners {
120 assert!(!r.id.is_empty(), "runner {} has empty id", r.name);
121 assert!(!r.name.is_empty(), "runner {} has empty name", r.id);
122 assert!(!r.provider.is_empty(), "runner {} has empty provider", r.id);
123 assert!(
124 !r.default_model.is_empty(),
125 "runner {} has empty default_model",
126 r.id
127 );
128 }
129 }
130}