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}
39
40#[derive(Args, Debug)]
41pub struct FlowValidateArgs {
42 #[arg(short = 'f', long = "file")]
44 pub file: PathBuf,
45 #[arg(long = "json")]
47 pub json: bool,
48}
49
50#[derive(Subcommand, Debug)]
51pub enum PackCommand {
52 Build(PackBuildArgs),
54 Run(PackRunArgs),
56 Verify(PackVerifyArgs),
58 Init(PackInitArgs),
60 New(PackNewArgs),
62}
63
64#[derive(Args, Debug)]
65pub struct PackBuildArgs {
66 #[arg(short = 'f', long = "file")]
68 pub file: PathBuf,
69 #[arg(short = 'o', long = "out")]
71 pub out: PathBuf,
72 #[arg(long = "sign", default_value = "dev", value_enum)]
74 pub sign: PackSignArg,
75 #[arg(long = "meta")]
77 pub meta: Option<PathBuf>,
78 #[arg(long = "component-dir", value_name = "DIR")]
80 pub component_dir: Option<PathBuf>,
81}
82
83#[derive(Args, Debug)]
84pub struct PackRunArgs {
85 #[arg(short = 'p', long = "pack")]
87 pub pack: PathBuf,
88 #[arg(long = "entry")]
90 pub entry: Option<String>,
91 #[arg(long = "input")]
93 pub input: Option<String>,
94 #[arg(long = "policy", default_value = "devok", value_enum)]
96 pub policy: RunPolicyArg,
97 #[arg(long = "otlp")]
99 pub otlp: Option<String>,
100 #[arg(long = "allow")]
102 pub allow: Option<String>,
103 #[arg(long = "mocks", default_value = "on", value_enum)]
105 pub mocks: MockSettingArg,
106 #[arg(long = "artifacts")]
108 pub artifacts: Option<PathBuf>,
109}
110
111#[derive(Args, Debug)]
112pub struct PackVerifyArgs {
113 #[arg(short = 'p', long = "pack")]
115 pub pack: PathBuf,
116 #[arg(long = "policy", default_value = "devok", value_enum)]
118 pub policy: VerifyPolicyArg,
119 #[arg(long = "json")]
121 pub json: bool,
122}
123
124#[derive(Args, Debug)]
125pub struct PackInitArgs {
126 pub from: String,
128 #[arg(long = "profile")]
130 pub profile: Option<String>,
131}
132
133#[derive(Args, Debug, Clone, Default)]
134#[command(disable_help_flag = true)]
135pub struct PackNewArgs {
136 #[arg(
138 value_name = "ARGS",
139 trailing_var_arg = true,
140 allow_hyphen_values = true
141 )]
142 pub passthrough: Vec<String>,
143}
144
145#[derive(Subcommand, Debug, Clone)]
146pub enum ComponentCommand {
147 Add(ComponentAddArgs),
149 #[command(external_subcommand)]
151 Passthrough(Vec<String>),
152}
153
154#[derive(Args, Debug, Clone)]
155pub struct ComponentAddArgs {
156 pub coordinate: String,
158 #[arg(long = "profile")]
160 pub profile: Option<String>,
161 #[arg(long = "intent", default_value = "dev", value_enum)]
163 pub intent: DevIntentArg,
164}
165
166#[cfg(feature = "mcp")]
167#[derive(Subcommand, Debug)]
168pub enum McpCommand {
169 Doctor(McpDoctorArgs),
171}
172
173#[cfg(feature = "mcp")]
174#[derive(Args, Debug)]
175pub struct McpDoctorArgs {
176 pub provider: String,
178 #[arg(long = "json")]
180 pub json: bool,
181}
182
183#[derive(Subcommand, Debug)]
184pub enum ConfigCommand {
185 Set(ConfigSetArgs),
187}
188
189#[derive(Args, Debug)]
190pub struct ConfigSetArgs {
191 pub key: String,
193 pub value: String,
195 #[arg(long = "file")]
197 pub file: Option<PathBuf>,
198}
199
200#[derive(Copy, Clone, Debug, ValueEnum)]
201pub enum PackSignArg {
202 Dev,
203 None,
204}
205
206#[derive(Copy, Clone, Debug, ValueEnum)]
207pub enum RunPolicyArg {
208 Strict,
209 Devok,
210}
211
212#[derive(Copy, Clone, Debug, ValueEnum)]
213pub enum VerifyPolicyArg {
214 Strict,
215 Devok,
216}
217
218#[derive(Copy, Clone, Debug, ValueEnum)]
219pub enum MockSettingArg {
220 On,
221 Off,
222}
223
224#[derive(Copy, Clone, Debug, ValueEnum)]
225pub enum DevIntentArg {
226 Dev,
227 Runtime,
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use clap::Parser;
234
235 #[test]
236 fn parses_component_passthrough_args() {
237 let cli = Cli::parse_from([
238 "greentic-dev",
239 "component",
240 "new",
241 "--name",
242 "demo",
243 "--json",
244 ]);
245 let Command::Component(ComponentCommand::Passthrough(args)) = cli.command else {
246 panic!("expected component passthrough variant");
247 };
248 assert_eq!(
249 args,
250 vec![
251 "new".to_string(),
252 "--name".into(),
253 "demo".into(),
254 "--json".into()
255 ]
256 );
257 }
258
259 #[test]
260 fn parses_pack_new_args() {
261 let cli = Cli::parse_from(["greentic-dev", "pack", "new", "--name", "demo-pack"]);
262 let Command::Pack(PackCommand::New(args)) = cli.command else {
263 panic!("expected pack new variant");
264 };
265 assert_eq!(
266 args.passthrough,
267 vec!["--name".to_string(), "demo-pack".to_string()]
268 );
269 }
270
271 #[test]
272 fn parses_config_set_command() {
273 let cli = Cli::parse_from([
274 "greentic-dev",
275 "config",
276 "set",
277 "defaults.component.org",
278 "ai.greentic",
279 "--file",
280 "/tmp/config.toml",
281 ]);
282 let Command::Config(ConfigCommand::Set(args)) = cli.command else {
283 panic!("expected config set variant");
284 };
285 assert_eq!(args.key, "defaults.component.org");
286 assert_eq!(args.value, "ai.greentic");
287 assert_eq!(
288 args.file.as_ref().map(|p| p.display().to_string()),
289 Some("/tmp/config.toml".into())
290 );
291 }
292}