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 Component(ComponentPassthroughArgs),
24 #[command(subcommand)]
26 Config(ConfigCommand),
27 #[cfg(feature = "mcp")]
29 #[command(subcommand)]
30 Mcp(McpCommand),
31}
32
33#[derive(Subcommand, Debug)]
34pub enum FlowCommand {
35 Validate(FlowValidateArgs),
37}
38
39#[derive(Args, Debug)]
40pub struct FlowValidateArgs {
41 #[arg(short = 'f', long = "file")]
43 pub file: PathBuf,
44 #[arg(long = "json")]
46 pub json: bool,
47}
48
49#[derive(Subcommand, Debug)]
50pub enum PackCommand {
51 Build(PackBuildArgs),
53 Run(PackRunArgs),
55 New(PackNewArgs),
57}
58
59#[derive(Args, Debug)]
60pub struct PackBuildArgs {
61 #[arg(short = 'f', long = "file")]
63 pub file: PathBuf,
64 #[arg(short = 'o', long = "out")]
66 pub out: PathBuf,
67 #[arg(long = "sign", default_value = "dev", value_enum)]
69 pub sign: PackSignArg,
70 #[arg(long = "meta")]
72 pub meta: Option<PathBuf>,
73 #[arg(long = "component-dir", value_name = "DIR")]
75 pub component_dir: Option<PathBuf>,
76}
77
78#[derive(Args, Debug)]
79pub struct PackRunArgs {
80 #[arg(short = 'p', long = "pack")]
82 pub pack: PathBuf,
83 #[arg(long = "entry")]
85 pub entry: Option<String>,
86 #[arg(long = "input")]
88 pub input: Option<String>,
89 #[arg(long = "policy", default_value = "devok", value_enum)]
91 pub policy: RunPolicyArg,
92 #[arg(long = "otlp")]
94 pub otlp: Option<String>,
95 #[arg(long = "allow")]
97 pub allow: Option<String>,
98 #[arg(long = "mocks", default_value = "on", value_enum)]
100 pub mocks: MockSettingArg,
101 #[arg(long = "artifacts")]
103 pub artifacts: Option<PathBuf>,
104}
105
106#[derive(Args, Debug, Clone, Default)]
107pub struct PackNewArgs {
108 #[arg(
110 value_name = "ARGS",
111 trailing_var_arg = true,
112 allow_hyphen_values = true
113 )]
114 pub passthrough: Vec<String>,
115}
116
117#[derive(Args, Debug, Clone, Default)]
118pub struct ComponentPassthroughArgs {
119 #[arg(
121 value_name = "ARGS",
122 trailing_var_arg = true,
123 allow_hyphen_values = true
124 )]
125 pub passthrough: Vec<String>,
126}
127
128#[cfg(feature = "mcp")]
129#[derive(Subcommand, Debug)]
130pub enum McpCommand {
131 Doctor(McpDoctorArgs),
133}
134
135#[cfg(feature = "mcp")]
136#[derive(Args, Debug)]
137pub struct McpDoctorArgs {
138 pub provider: String,
140 #[arg(long = "json")]
142 pub json: bool,
143}
144
145#[derive(Subcommand, Debug)]
146pub enum ConfigCommand {
147 Set(ConfigSetArgs),
149}
150
151#[derive(Args, Debug)]
152pub struct ConfigSetArgs {
153 pub key: String,
155 pub value: String,
157 #[arg(long = "file")]
159 pub file: Option<PathBuf>,
160}
161
162#[derive(Copy, Clone, Debug, ValueEnum)]
163pub enum PackSignArg {
164 Dev,
165 None,
166}
167
168#[derive(Copy, Clone, Debug, ValueEnum)]
169pub enum RunPolicyArg {
170 Strict,
171 Devok,
172}
173
174#[derive(Copy, Clone, Debug, ValueEnum)]
175pub enum MockSettingArg {
176 On,
177 Off,
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use clap::Parser;
184
185 #[test]
186 fn parses_component_passthrough_args() {
187 let cli = Cli::parse_from([
188 "greentic-dev",
189 "component",
190 "new",
191 "--name",
192 "demo",
193 "--json",
194 ]);
195 let Command::Component(args) = cli.command else {
196 panic!("expected component passthrough variant");
197 };
198 assert_eq!(
199 args.passthrough,
200 vec![
201 "new".to_string(),
202 "--name".into(),
203 "demo".into(),
204 "--json".into()
205 ]
206 );
207 }
208
209 #[test]
210 fn parses_pack_new_args() {
211 let cli = Cli::parse_from(["greentic-dev", "pack", "new", "--name", "demo-pack"]);
212 let Command::Pack(PackCommand::New(args)) = cli.command else {
213 panic!("expected pack new variant");
214 };
215 assert_eq!(
216 args.passthrough,
217 vec!["--name".to_string(), "demo-pack".to_string()]
218 );
219 }
220
221 #[test]
222 fn parses_config_set_command() {
223 let cli = Cli::parse_from([
224 "greentic-dev",
225 "config",
226 "set",
227 "defaults.component.org",
228 "ai.greentic",
229 "--file",
230 "/tmp/config.toml",
231 ]);
232 let Command::Config(ConfigCommand::Set(args)) = cli.command else {
233 panic!("expected config set variant");
234 };
235 assert_eq!(args.key, "defaults.component.org");
236 assert_eq!(args.value, "ai.greentic");
237 assert_eq!(
238 args.file.as_ref().map(|p| p.display().to_string()),
239 Some("/tmp/config.toml".into())
240 );
241 }
242}