1use clap::{Parser, Subcommand};
3
4#[derive(Parser, Debug)]
5#[command(
6 name = "studio-worker",
7 version,
8 about = "Studio worker — pull-based generation agent (image / llm / audio / video)"
9)]
10pub struct Cli {
11 #[arg(long, global = true)]
13 pub config: Option<String>,
14 #[command(subcommand)]
15 pub command: Command,
16}
17
18#[derive(Subcommand, Debug, PartialEq)]
19pub enum Command {
20 Run {
22 #[arg(long)]
25 wait_for_lock: bool,
26 },
27 Register {
35 #[arg(long)]
36 api_base_url: Option<String>,
37 #[arg(long)]
38 reset: bool,
39 },
40 Status,
42 Setup,
46 InstallService,
48 UninstallService,
50 SetThreshold { gb: f32 },
52 Config,
54 CheckUpdate,
56 Ui,
58}
59
60impl Command {
61 pub fn name(&self) -> &'static str {
66 match self {
67 Command::Run { .. } => "run",
68 Command::Setup => "setup",
69 Command::Register { .. } => "register",
70 Command::Status => "status",
71 Command::InstallService => "install-service",
72 Command::UninstallService => "uninstall-service",
73 Command::SetThreshold { .. } => "set-threshold",
74 Command::Config => "config",
75 Command::CheckUpdate => "check-update",
76 Command::Ui => "ui",
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use clap::Parser;
85
86 #[test]
87 fn run_can_wait_for_the_lock_under_a_supervisor() {
88 let cli = Cli::try_parse_from(["studio-worker", "run", "--wait-for-lock"]).unwrap();
89 assert!(matches!(
90 cli.command,
91 Command::Run {
92 wait_for_lock: true
93 }
94 ));
95 }
96
97 #[test]
98 fn parses_run() {
99 let cli = Cli::parse_from(["studio-worker", "run"]);
100 assert!(matches!(
101 cli.command,
102 Command::Run {
103 wait_for_lock: false
104 }
105 ));
106 assert!(cli.config.is_none());
107 }
108
109 #[test]
110 fn parses_run_with_config_override() {
111 let cli = Cli::parse_from(["studio-worker", "--config", "/etc/x.toml", "run"]);
112 assert_eq!(cli.config.as_deref(), Some("/etc/x.toml"));
113 assert!(matches!(
114 cli.command,
115 Command::Run {
116 wait_for_lock: false
117 }
118 ));
119 }
120
121 #[test]
122 fn parses_register_with_overrides() {
123 let cli = Cli::parse_from([
124 "studio-worker",
125 "register",
126 "--api-base-url",
127 "https://example.invalid",
128 ]);
129 match cli.command {
130 Command::Register {
131 api_base_url,
132 reset,
133 } => {
134 assert_eq!(api_base_url.as_deref(), Some("https://example.invalid"));
135 assert!(!reset);
136 }
137 other => panic!("expected register, got {other:?}"),
138 }
139 }
140
141 #[test]
142 fn parses_register_with_reset() {
143 let cli = Cli::parse_from(["studio-worker", "register", "--reset"]);
144 match cli.command {
145 Command::Register {
146 api_base_url,
147 reset,
148 } => {
149 assert!(api_base_url.is_none());
150 assert!(reset);
151 }
152 other => panic!("expected register, got {other:?}"),
153 }
154 }
155
156 #[test]
157 fn parses_bare_register() {
158 let cli = Cli::parse_from(["studio-worker", "register"]);
159 assert!(matches!(
160 cli.command,
161 Command::Register {
162 api_base_url: None,
163 reset: false,
164 }
165 ));
166 }
167
168 #[test]
169 fn parses_set_threshold_with_float() {
170 let cli = Cli::parse_from(["studio-worker", "set-threshold", "12.5"]);
171 match cli.command {
172 Command::SetThreshold { gb } => assert!((gb - 12.5).abs() < 1e-6),
173 other => panic!("expected set-threshold, got {other:?}"),
174 }
175 }
176
177 #[test]
178 fn name_is_stable_kebab_case_for_every_subcommand() {
179 assert_eq!(
180 Command::Run {
181 wait_for_lock: false
182 }
183 .name(),
184 "run"
185 );
186 assert_eq!(
187 Command::Register {
188 api_base_url: None,
189 reset: false
190 }
191 .name(),
192 "register"
193 );
194 assert_eq!(Command::Status.name(), "status");
195 assert_eq!(Command::Setup.name(), "setup");
196 assert_eq!(Command::InstallService.name(), "install-service");
197 assert_eq!(Command::UninstallService.name(), "uninstall-service");
198 assert_eq!(Command::SetThreshold { gb: 1.0 }.name(), "set-threshold");
199 assert_eq!(Command::Config.name(), "config");
200 assert_eq!(Command::CheckUpdate.name(), "check-update");
201 assert_eq!(Command::Ui.name(), "ui");
202 }
203
204 #[test]
205 fn parses_all_simple_subcommands() {
206 let cases = [
207 ("status", Command::Status),
208 ("setup", Command::Setup),
209 ("install-service", Command::InstallService),
210 ("uninstall-service", Command::UninstallService),
211 ("config", Command::Config),
212 ("check-update", Command::CheckUpdate),
213 ("ui", Command::Ui),
214 ];
215 for (name, expected) in cases {
216 let cli = Cli::parse_from(["studio-worker", name]);
217 assert_eq!(cli.command, expected, "parsing `{name}`");
218 }
219 }
220}