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}
57
58#[derive(Args, Debug)]
59pub struct PackBuildArgs {
60 #[arg(short = 'f', long = "file")]
62 pub file: PathBuf,
63 #[arg(short = 'o', long = "out")]
65 pub out: PathBuf,
66 #[arg(long = "sign", default_value = "dev", value_enum)]
68 pub sign: PackSignArg,
69 #[arg(long = "meta")]
71 pub meta: Option<PathBuf>,
72 #[arg(long = "component-dir", value_name = "DIR")]
74 pub component_dir: Option<PathBuf>,
75}
76
77#[derive(Args, Debug)]
78pub struct PackRunArgs {
79 #[arg(short = 'p', long = "pack")]
81 pub pack: PathBuf,
82 #[arg(long = "entry")]
84 pub entry: Option<String>,
85 #[arg(long = "input")]
87 pub input: Option<String>,
88 #[arg(long = "policy", default_value = "devok", value_enum)]
90 pub policy: RunPolicyArg,
91 #[arg(long = "otlp")]
93 pub otlp: Option<String>,
94 #[arg(long = "allow")]
96 pub allow: Option<String>,
97 #[arg(long = "mocks", default_value = "on", value_enum)]
99 pub mocks: MockSettingArg,
100 #[arg(long = "artifacts")]
102 pub artifacts: Option<PathBuf>,
103}
104
105#[derive(Subcommand, Debug)]
106pub enum ComponentCommand {
107 Inspect(ComponentInspectArgs),
109 Templates(ComponentTemplatesArgs),
111 New(ComponentNewArgs),
113 Validate(ComponentValidateArgs),
115 Doctor(ComponentDoctorArgs),
117}
118
119#[derive(Args, Debug, Clone)]
120pub struct ComponentInspectArgs {
121 pub target: String,
123 #[arg(long = "json")]
125 pub json: bool,
126}
127
128#[derive(Args, Debug, Clone, Default)]
129pub struct ComponentTemplatesArgs {
130 #[arg(long = "json")]
132 pub json: bool,
133 #[arg(long = "telemetry")]
135 pub telemetry: bool,
136}
137
138#[derive(Args, Debug, Clone, Default)]
139pub struct ComponentNewArgs {
140 #[arg(long = "name", value_name = "NAME")]
142 pub name: Option<String>,
143 #[arg(long = "path", value_name = "DIR")]
145 pub path: Option<PathBuf>,
146 #[arg(long = "template", value_name = "ID")]
148 pub template: Option<String>,
149 #[arg(long = "org", value_name = "ORG")]
151 pub org: Option<String>,
152 #[arg(long = "version", value_name = "SEMVER")]
154 pub version: Option<String>,
155 #[arg(long = "license", value_name = "ID")]
157 pub license: Option<String>,
158 #[arg(long = "wit-world", value_name = "WORLD")]
160 pub wit_world: Option<String>,
161 #[arg(long = "non-interactive")]
163 pub non_interactive: bool,
164 #[arg(long = "no-check")]
166 pub no_check: bool,
167 #[arg(long = "json")]
169 pub json: bool,
170 #[arg(long = "telemetry")]
172 pub telemetry: bool,
173}
174
175#[derive(Args, Debug, Clone, Default)]
176pub struct ComponentValidateArgs {
177 #[arg(long = "path", value_name = "DIR")]
179 pub path: Option<PathBuf>,
180 #[arg(long = "telemetry")]
182 pub telemetry: bool,
183}
184
185#[derive(Args, Debug, Clone, Default)]
186pub struct ComponentDoctorArgs {
187 #[arg(long = "path", value_name = "DIR")]
189 pub path: Option<PathBuf>,
190 #[arg(long = "telemetry")]
192 pub telemetry: bool,
193}
194
195#[cfg(feature = "mcp")]
196#[derive(Subcommand, Debug)]
197pub enum McpCommand {
198 Doctor(McpDoctorArgs),
200}
201
202#[cfg(feature = "mcp")]
203#[derive(Args, Debug)]
204pub struct McpDoctorArgs {
205 pub provider: String,
207 #[arg(long = "json")]
209 pub json: bool,
210}
211
212#[derive(Subcommand, Debug)]
213pub enum ConfigCommand {
214 Set(ConfigSetArgs),
216}
217
218#[derive(Args, Debug)]
219pub struct ConfigSetArgs {
220 pub key: String,
222 pub value: String,
224 #[arg(long = "file")]
226 pub file: Option<PathBuf>,
227}
228
229#[derive(Copy, Clone, Debug, ValueEnum)]
230pub enum PackSignArg {
231 Dev,
232 None,
233}
234
235#[derive(Copy, Clone, Debug, ValueEnum)]
236pub enum RunPolicyArg {
237 Strict,
238 Devok,
239}
240
241#[derive(Copy, Clone, Debug, ValueEnum)]
242pub enum MockSettingArg {
243 On,
244 Off,
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use clap::Parser;
251
252 #[test]
253 fn parses_component_new_arguments() {
254 let cli = Cli::parse_from([
255 "greentic-dev",
256 "component",
257 "new",
258 "--name",
259 "demo",
260 "--path",
261 "./demo",
262 "--template",
263 "rust",
264 "--org",
265 "ai.greentic",
266 "--version",
267 "0.1.0",
268 "--license",
269 "Apache-2.0",
270 "--wit-world",
271 "component:demo",
272 "--non-interactive",
273 "--no-check",
274 "--json",
275 "--telemetry",
276 ]);
277
278 let Command::Component(ComponentCommand::New(args)) = cli.command else {
279 panic!("expected component new variant");
280 };
281 assert_eq!(args.name.as_deref(), Some("demo"));
282 assert_eq!(
283 args.path.as_ref().map(|p| p.display().to_string()),
284 Some("./demo".into())
285 );
286 assert_eq!(args.template.as_deref(), Some("rust"));
287 assert_eq!(args.org.as_deref(), Some("ai.greentic"));
288 assert_eq!(args.version.as_deref(), Some("0.1.0"));
289 assert_eq!(args.license.as_deref(), Some("Apache-2.0"));
290 assert_eq!(args.wit_world.as_deref(), Some("component:demo"));
291 assert!(args.non_interactive);
292 assert!(args.no_check);
293 assert!(args.json);
294 assert!(args.telemetry);
295 }
296
297 #[test]
298 fn parses_component_templates_flag() {
299 let cli = Cli::parse_from([
300 "greentic-dev",
301 "component",
302 "templates",
303 "--json",
304 "--telemetry",
305 ]);
306 let Command::Component(ComponentCommand::Templates(args)) = cli.command else {
307 panic!("expected component templates variant");
308 };
309 assert!(args.json);
310 assert!(args.telemetry);
311 }
312
313 #[test]
314 fn parses_config_set_command() {
315 let cli = Cli::parse_from([
316 "greentic-dev",
317 "config",
318 "set",
319 "defaults.component.org",
320 "ai.greentic",
321 "--file",
322 "/tmp/config.toml",
323 ]);
324 let Command::Config(ConfigCommand::Set(args)) = cli.command else {
325 panic!("expected config set variant");
326 };
327 assert_eq!(args.key, "defaults.component.org");
328 assert_eq!(args.value, "ai.greentic");
329 assert_eq!(
330 args.file.as_ref().map(|p| p.display().to_string()),
331 Some("/tmp/config.toml".into())
332 );
333 }
334}