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 Verify(PackVerifyArgs),
57 New(PackNewArgs),
59}
60
61#[derive(Args, Debug)]
62pub struct PackBuildArgs {
63 #[arg(short = 'f', long = "file")]
65 pub file: PathBuf,
66 #[arg(short = 'o', long = "out")]
68 pub out: PathBuf,
69 #[arg(long = "sign", default_value = "dev", value_enum)]
71 pub sign: PackSignArg,
72 #[arg(long = "meta")]
74 pub meta: Option<PathBuf>,
75 #[arg(long = "component-dir", value_name = "DIR")]
77 pub component_dir: Option<PathBuf>,
78}
79
80#[derive(Args, Debug)]
81pub struct PackRunArgs {
82 #[arg(short = 'p', long = "pack")]
84 pub pack: PathBuf,
85 #[arg(long = "entry")]
87 pub entry: Option<String>,
88 #[arg(long = "input")]
90 pub input: Option<String>,
91 #[arg(long = "policy", default_value = "devok", value_enum)]
93 pub policy: RunPolicyArg,
94 #[arg(long = "otlp")]
96 pub otlp: Option<String>,
97 #[arg(long = "allow")]
99 pub allow: Option<String>,
100 #[arg(long = "mocks", default_value = "on", value_enum)]
102 pub mocks: MockSettingArg,
103 #[arg(long = "artifacts")]
105 pub artifacts: Option<PathBuf>,
106}
107
108#[derive(Args, Debug)]
109pub struct PackVerifyArgs {
110 #[arg(short = 'p', long = "pack")]
112 pub pack: PathBuf,
113 #[arg(long = "policy", default_value = "devok", value_enum)]
115 pub policy: VerifyPolicyArg,
116 #[arg(long = "json")]
118 pub json: bool,
119}
120
121#[derive(Args, Debug, Clone, Default)]
122#[command(disable_help_flag = true)]
123pub struct PackNewArgs {
124 #[arg(
126 value_name = "ARGS",
127 trailing_var_arg = true,
128 allow_hyphen_values = true
129 )]
130 pub passthrough: Vec<String>,
131}
132
133#[derive(Args, Debug, Clone, Default)]
134pub struct ComponentPassthroughArgs {
135 #[arg(
137 value_name = "ARGS",
138 trailing_var_arg = true,
139 allow_hyphen_values = true
140 )]
141 pub passthrough: Vec<String>,
142}
143
144#[cfg(feature = "mcp")]
145#[derive(Subcommand, Debug)]
146pub enum McpCommand {
147 Doctor(McpDoctorArgs),
149}
150
151#[cfg(feature = "mcp")]
152#[derive(Args, Debug)]
153pub struct McpDoctorArgs {
154 pub provider: String,
156 #[arg(long = "json")]
158 pub json: bool,
159}
160
161#[derive(Subcommand, Debug)]
162pub enum ConfigCommand {
163 Set(ConfigSetArgs),
165}
166
167#[derive(Args, Debug)]
168pub struct ConfigSetArgs {
169 pub key: String,
171 pub value: String,
173 #[arg(long = "file")]
175 pub file: Option<PathBuf>,
176}
177
178#[derive(Copy, Clone, Debug, ValueEnum)]
179pub enum PackSignArg {
180 Dev,
181 None,
182}
183
184#[derive(Copy, Clone, Debug, ValueEnum)]
185pub enum RunPolicyArg {
186 Strict,
187 Devok,
188}
189
190#[derive(Copy, Clone, Debug, ValueEnum)]
191pub enum VerifyPolicyArg {
192 Strict,
193 Devok,
194}
195
196#[derive(Copy, Clone, Debug, ValueEnum)]
197pub enum MockSettingArg {
198 On,
199 Off,
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use clap::Parser;
206
207 #[test]
208 fn parses_component_passthrough_args() {
209 let cli = Cli::parse_from([
210 "greentic-dev",
211 "component",
212 "new",
213 "--name",
214 "demo",
215 "--json",
216 ]);
217 let Command::Component(args) = cli.command else {
218 panic!("expected component passthrough variant");
219 };
220 assert_eq!(
221 args.passthrough,
222 vec![
223 "new".to_string(),
224 "--name".into(),
225 "demo".into(),
226 "--json".into()
227 ]
228 );
229 }
230
231 #[test]
232 fn parses_pack_new_args() {
233 let cli = Cli::parse_from(["greentic-dev", "pack", "new", "--name", "demo-pack"]);
234 let Command::Pack(PackCommand::New(args)) = cli.command else {
235 panic!("expected pack new variant");
236 };
237 assert_eq!(
238 args.passthrough,
239 vec!["--name".to_string(), "demo-pack".to_string()]
240 );
241 }
242
243 #[test]
244 fn parses_config_set_command() {
245 let cli = Cli::parse_from([
246 "greentic-dev",
247 "config",
248 "set",
249 "defaults.component.org",
250 "ai.greentic",
251 "--file",
252 "/tmp/config.toml",
253 ]);
254 let Command::Config(ConfigCommand::Set(args)) = cli.command else {
255 panic!("expected config set variant");
256 };
257 assert_eq!(args.key, "defaults.component.org");
258 assert_eq!(args.value, "ai.greentic");
259 assert_eq!(
260 args.file.as_ref().map(|p| p.display().to_string()),
261 Some("/tmp/config.toml".into())
262 );
263 }
264}