1use std::path::PathBuf;
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4
5#[derive(Parser, Debug)]
6#[command(name = "greentic-dev")]
7#[command(version)]
8#[command(about = "Greentic developer tooling CLI")]
9pub struct Cli {
10 #[command(subcommand)]
11 pub command: Command,
12}
13
14#[derive(Subcommand, Debug)]
15pub enum Command {
16 #[command(subcommand)]
18 Flow(FlowCommand),
19 #[command(subcommand)]
21 Pack(PackCommand),
22 #[command(subcommand)]
24 Component(ComponentCommand),
25 #[command(subcommand)]
27 Config(ConfigCommand),
28 #[cfg(feature = "mcp")]
30 #[command(subcommand)]
31 Mcp(McpCommand),
32}
33
34#[derive(Subcommand, Debug)]
35pub enum FlowCommand {
36 Validate(FlowValidateArgs),
38 AddStep(FlowAddStepArgs),
40}
41
42#[derive(Args, Debug)]
43pub struct FlowValidateArgs {
44 #[arg(short = 'f', long = "file")]
46 pub file: PathBuf,
47 #[arg(long = "json")]
49 pub json: bool,
50}
51
52#[derive(Args, Debug)]
53pub struct FlowAddStepArgs {
54 pub flow_id: String,
56 #[arg(long = "coordinate")]
58 pub coordinate: Option<String>,
59 #[arg(long = "profile")]
61 pub profile: Option<String>,
62 #[arg(long = "mode", value_enum)]
64 pub mode: Option<ConfigFlowModeArg>,
65 #[arg(long = "after")]
67 pub after: Option<String>,
68}
69
70#[derive(Subcommand, Debug)]
71pub enum PackCommand {
72 Build(PackBuildArgs),
74 Run(PackRunArgs),
76 Verify(PackVerifyArgs),
78 Init(PackInitArgs),
80 New(PackNewArgs),
82}
83
84#[derive(Args, Debug)]
85pub struct PackBuildArgs {
86 #[arg(short = 'f', long = "file")]
88 pub file: PathBuf,
89 #[arg(short = 'o', long = "out")]
91 pub out: PathBuf,
92 #[arg(long = "sign", default_value = "dev", value_enum)]
94 pub sign: PackSignArg,
95 #[arg(long = "meta")]
97 pub meta: Option<PathBuf>,
98 #[arg(long = "component-dir", value_name = "DIR")]
100 pub component_dir: Option<PathBuf>,
101}
102
103#[derive(Args, Debug)]
104pub struct PackRunArgs {
105 #[arg(short = 'p', long = "pack")]
107 pub pack: PathBuf,
108 #[arg(long = "entry")]
110 pub entry: Option<String>,
111 #[arg(long = "input")]
113 pub input: Option<String>,
114 #[arg(long = "policy", default_value = "devok", value_enum)]
116 pub policy: RunPolicyArg,
117 #[arg(long = "otlp")]
119 pub otlp: Option<String>,
120 #[arg(long = "allow")]
122 pub allow: Option<String>,
123 #[arg(long = "mocks", default_value = "on", value_enum)]
125 pub mocks: MockSettingArg,
126 #[arg(long = "artifacts")]
128 pub artifacts: Option<PathBuf>,
129}
130
131#[derive(Args, Debug)]
132pub struct PackVerifyArgs {
133 #[arg(short = 'p', long = "pack")]
135 pub pack: PathBuf,
136 #[arg(long = "policy", default_value = "devok", value_enum)]
138 pub policy: VerifyPolicyArg,
139 #[arg(long = "json")]
141 pub json: bool,
142}
143
144#[derive(Args, Debug)]
145pub struct PackInitArgs {
146 pub from: String,
148 #[arg(long = "profile")]
150 pub profile: Option<String>,
151}
152
153#[derive(Args, Debug, Clone, Default)]
154#[command(disable_help_flag = true)]
155pub struct PackNewArgs {
156 #[arg(
158 value_name = "ARGS",
159 trailing_var_arg = true,
160 allow_hyphen_values = true
161 )]
162 pub passthrough: Vec<String>,
163}
164
165#[derive(Subcommand, Debug, Clone)]
166pub enum ComponentCommand {
167 Add(ComponentAddArgs),
169 #[command(external_subcommand)]
171 Passthrough(Vec<String>),
172}
173
174#[derive(Args, Debug, Clone)]
175pub struct ComponentAddArgs {
176 pub coordinate: String,
178 #[arg(long = "profile")]
180 pub profile: Option<String>,
181 #[arg(long = "intent", default_value = "dev", value_enum)]
183 pub intent: DevIntentArg,
184}
185
186#[cfg(feature = "mcp")]
187#[derive(Subcommand, Debug)]
188pub enum McpCommand {
189 Doctor(McpDoctorArgs),
191}
192
193#[cfg(feature = "mcp")]
194#[derive(Args, Debug)]
195pub struct McpDoctorArgs {
196 pub provider: String,
198 #[arg(long = "json")]
200 pub json: bool,
201}
202
203#[derive(Subcommand, Debug)]
204pub enum ConfigCommand {
205 Set(ConfigSetArgs),
207}
208
209#[derive(Args, Debug)]
210pub struct ConfigSetArgs {
211 pub key: String,
213 pub value: String,
215 #[arg(long = "file")]
217 pub file: Option<PathBuf>,
218}
219
220#[derive(Copy, Clone, Debug, ValueEnum)]
221pub enum PackSignArg {
222 Dev,
223 None,
224}
225
226#[derive(Copy, Clone, Debug, ValueEnum)]
227pub enum RunPolicyArg {
228 Strict,
229 Devok,
230}
231
232#[derive(Copy, Clone, Debug, ValueEnum)]
233pub enum VerifyPolicyArg {
234 Strict,
235 Devok,
236}
237
238#[derive(Copy, Clone, Debug, ValueEnum)]
239pub enum MockSettingArg {
240 On,
241 Off,
242}
243
244#[derive(Copy, Clone, Debug, ValueEnum)]
245pub enum ConfigFlowModeArg {
246 Default,
247 Custom,
248}
249#[derive(Copy, Clone, Debug, ValueEnum)]
250pub enum DevIntentArg {
251 Dev,
252 Runtime,
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use clap::Parser;
259
260 #[test]
261 fn parses_component_passthrough_args() {
262 let cli = Cli::parse_from([
263 "greentic-dev",
264 "component",
265 "new",
266 "--name",
267 "demo",
268 "--json",
269 ]);
270 let Command::Component(ComponentCommand::Passthrough(args)) = cli.command else {
271 panic!("expected component passthrough variant");
272 };
273 assert_eq!(
274 args,
275 vec![
276 "new".to_string(),
277 "--name".into(),
278 "demo".into(),
279 "--json".into()
280 ]
281 );
282 }
283
284 #[test]
285 fn parses_pack_new_args() {
286 let cli = Cli::parse_from(["greentic-dev", "pack", "new", "--name", "demo-pack"]);
287 let Command::Pack(PackCommand::New(args)) = cli.command else {
288 panic!("expected pack new variant");
289 };
290 assert_eq!(
291 args.passthrough,
292 vec!["--name".to_string(), "demo-pack".to_string()]
293 );
294 }
295
296 #[test]
297 fn parses_config_set_command() {
298 let cli = Cli::parse_from([
299 "greentic-dev",
300 "config",
301 "set",
302 "defaults.component.org",
303 "ai.greentic",
304 "--file",
305 "/tmp/config.toml",
306 ]);
307 let Command::Config(ConfigCommand::Set(args)) = cli.command else {
308 panic!("expected config set variant");
309 };
310 assert_eq!(args.key, "defaults.component.org");
311 assert_eq!(args.value, "ai.greentic");
312 assert_eq!(
313 args.file.as_ref().map(|p| p.display().to_string()),
314 Some("/tmp/config.toml".into())
315 );
316 }
317}