1use std::{ffi::OsString, path::PathBuf};
2
3use crate::secrets_cli::SecretsCommand;
4use clap::{Arg, ArgAction, Args, CommandFactory, Parser, Subcommand};
5
6#[derive(Parser, Debug)]
7#[command(name = "greentic-dev")]
8#[command(version)]
9#[command(about = "cli.root.about")]
10pub struct Cli {
11 #[command(subcommand)]
12 pub command: Command,
13}
14
15pub fn localized_help_command(locale: &str) -> clap::Command {
16 let mut command = Cli::command()
17 .about(crate::i18n::t(locale, "cli.root.about"))
18 .disable_help_subcommand(true)
19 .disable_help_flag(true)
20 .disable_version_flag(true)
21 .arg(
22 Arg::new("help")
23 .short('h')
24 .long("help")
25 .action(ArgAction::Help)
26 .help(crate::i18n::t(locale, "cli.help.flag")),
27 )
28 .arg(
29 Arg::new("version")
30 .short('V')
31 .long("version")
32 .action(ArgAction::Version)
33 .help(crate::i18n::t(locale, "cli.version.flag")),
34 )
35 .arg(
36 Arg::new("locale")
37 .long("locale")
38 .global(true)
39 .value_name("LOCALE")
40 .help(crate::i18n::t(locale, "cli.option.locale")),
41 );
42
43 for (name, key) in [
44 ("flow", "cli.command.flow.about"),
45 ("pack", "cli.command.pack.about"),
46 ("component", "cli.command.component.about"),
47 ("config", "cli.command.config.about"),
48 ("coverage", "cli.command.coverage.about"),
49 ("mcp", "cli.command.mcp.about"),
50 ("gui", "cli.command.gui.about"),
51 ("secrets", "cli.command.secrets.about"),
52 ("tools", "cli.command.tools.about"),
53 ("install", "cli.command.install.about"),
54 ("cbor", "cli.command.cbor.about"),
55 ("wizard", "cli.command.wizard.about"),
56 ] {
57 command = command.mut_subcommand(name, |sub| sub.about(crate::i18n::t(locale, key)));
58 }
59
60 command = command.mut_subcommand("secrets", |sub| {
61 sub.about(crate::i18n::t(locale, "cli.command.secrets.about"))
62 .mut_subcommand("init", |sub| {
63 sub.about(crate::i18n::t(locale, "cli.command.secrets.init.about"))
64 .mut_arg("pack", |arg| {
65 arg.help(crate::i18n::t(locale, "cli.command.secrets.init.pack"))
66 })
67 .mut_arg("passthrough", |arg| {
68 arg.help(crate::i18n::t(
69 locale,
70 "cli.command.secrets.init.passthrough",
71 ))
72 })
73 })
74 });
75 command = command
76 .mut_subcommand("config", |sub| {
77 sub.about(crate::i18n::t(locale, "cli.command.config.about"))
78 .mut_subcommand("set", |sub| {
79 sub.about(crate::i18n::t(locale, "cli.command.config.set.about"))
80 .mut_arg("key", |arg| {
81 arg.help(crate::i18n::t(locale, "cli.command.config.set.key"))
82 })
83 .mut_arg("value", |arg| {
84 arg.help(crate::i18n::t(locale, "cli.command.config.set.value"))
85 })
86 .mut_arg("file", |arg| {
87 arg.help(crate::i18n::t(locale, "cli.command.config.set.file"))
88 })
89 })
90 })
91 .mut_subcommand("mcp", |sub| {
92 sub.about(crate::i18n::t(locale, "cli.command.mcp.about"))
93 .mut_subcommand("doctor", |sub| {
94 sub.about(crate::i18n::t(locale, "cli.command.mcp.doctor.about"))
95 .mut_arg("provider", |arg| {
96 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.provider"))
97 })
98 .mut_arg("json", |arg| {
99 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.json"))
100 })
101 })
102 })
103 .mut_subcommand("tools", |sub| {
104 sub.about(crate::i18n::t(locale, "cli.command.tools.about"))
105 .mut_subcommand("install", |sub| {
106 sub.about(crate::i18n::t(locale, "cli.command.tools.install.about"))
107 .mut_arg("latest", |arg| {
108 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
109 })
110 })
111 })
112 .mut_subcommand("install", |sub| {
113 sub.about(crate::i18n::t(locale, "cli.command.install.about"))
114 .mut_subcommand("tools", |sub| {
115 sub.about(crate::i18n::t(locale, "cli.command.install.tools.about"))
116 .mut_arg("latest", |arg| {
117 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
118 })
119 })
120 .mut_arg("tenant", |arg| {
121 arg.help(crate::i18n::t(locale, "cli.command.install.tenant"))
122 })
123 .mut_arg("token", |arg| {
124 arg.help(crate::i18n::t(locale, "cli.command.install.token"))
125 })
126 .mut_arg("bin_dir", |arg| {
127 arg.help(crate::i18n::t(locale, "cli.command.install.bin_dir"))
128 })
129 .mut_arg("docs_dir", |arg| {
130 arg.help(crate::i18n::t(locale, "cli.command.install.docs_dir"))
131 })
132 .mut_arg("locale", |arg| {
133 arg.help(crate::i18n::t(locale, "cli.command.install.locale"))
134 })
135 })
136 .mut_subcommand("cbor", |sub| {
137 sub.about(crate::i18n::t(locale, "cli.command.cbor.about"))
138 .mut_arg("path", |arg| {
139 arg.help(crate::i18n::t(locale, "cli.command.cbor.path"))
140 })
141 })
142 .mut_subcommand("coverage", |sub| {
143 sub.about(crate::i18n::t(locale, "cli.command.coverage.about"))
144 .mut_arg("skip_run", |arg| {
145 arg.help(crate::i18n::t(locale, "cli.command.coverage.skip_run"))
146 })
147 })
148 .mut_subcommand("wizard", |sub| {
149 sub.about(crate::i18n::t(locale, "cli.command.wizard.about"))
150 .mut_arg("answers", |arg| {
151 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
152 })
153 .mut_arg("frontend", |arg| {
154 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
155 })
156 .mut_arg("locale", |arg| {
157 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
158 })
159 .mut_arg("emit_answers", |arg| {
160 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
161 })
162 .mut_arg("schema", |arg| {
163 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema"))
164 .long_help(crate::i18n::t(locale, "cli.command.wizard.schema_long"))
165 })
166 .mut_arg("schema_version", |arg| {
167 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
168 })
169 .mut_arg("migrate", |arg| {
170 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
171 })
172 .mut_arg("out", |arg| {
173 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
174 })
175 .mut_arg("dry_run", |arg| {
176 arg.help(crate::i18n::t(locale, "cli.command.wizard.dry_run"))
177 })
178 .mut_arg("yes", |arg| {
179 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
180 })
181 .mut_arg("non_interactive", |arg| {
182 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
183 })
184 .mut_arg("unsafe_commands", |arg| {
185 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
186 })
187 .mut_arg("allow_destructive", |arg| {
188 arg.help(crate::i18n::t(
189 locale,
190 "cli.command.wizard.allow_destructive",
191 ))
192 })
193 .mut_subcommand("validate", |sub| {
194 sub.about(crate::i18n::t(locale, "cli.command.wizard.validate.about"))
195 .mut_arg("answers", |arg| {
196 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
197 })
198 .mut_arg("frontend", |arg| {
199 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
200 })
201 .mut_arg("locale", |arg| {
202 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
203 })
204 .mut_arg("emit_answers", |arg| {
205 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
206 })
207 .mut_arg("schema_version", |arg| {
208 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
209 })
210 .mut_arg("migrate", |arg| {
211 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
212 })
213 .mut_arg("out", |arg| {
214 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
215 })
216 })
217 .mut_subcommand("apply", |sub| {
218 sub.about(crate::i18n::t(locale, "cli.command.wizard.apply.about"))
219 .mut_arg("answers", |arg| {
220 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
221 })
222 .mut_arg("frontend", |arg| {
223 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
224 })
225 .mut_arg("locale", |arg| {
226 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
227 })
228 .mut_arg("emit_answers", |arg| {
229 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
230 })
231 .mut_arg("schema_version", |arg| {
232 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
233 })
234 .mut_arg("migrate", |arg| {
235 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
236 })
237 .mut_arg("out", |arg| {
238 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
239 })
240 .mut_arg("yes", |arg| {
241 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
242 })
243 .mut_arg("non_interactive", |arg| {
244 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
245 })
246 .mut_arg("unsafe_commands", |arg| {
247 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
248 })
249 .mut_arg("allow_destructive", |arg| {
250 arg.help(crate::i18n::t(
251 locale,
252 "cli.command.wizard.allow_destructive",
253 ))
254 })
255 })
256 });
257
258 localize_help_tree(command, locale, true)
259}
260
261fn localize_help_tree(mut command: clap::Command, locale: &str, is_root: bool) -> clap::Command {
262 command = command
263 .disable_help_subcommand(true)
264 .disable_help_flag(true);
265 let arg_ids = command
266 .get_arguments()
267 .map(|arg| arg.get_id().as_str().to_string())
268 .collect::<Vec<_>>();
269 if arg_ids.iter().any(|id| id == "help") {
270 command = command.mut_arg("help", |arg| {
271 arg.help(crate::i18n::t(locale, "cli.help.flag"))
272 });
273 } else {
274 command = command.arg(
275 Arg::new("help")
276 .short('h')
277 .long("help")
278 .action(ArgAction::Help)
279 .help(crate::i18n::t(locale, "cli.help.flag")),
280 );
281 }
282 if is_root && arg_ids.iter().any(|id| id == "version") {
283 command = command.mut_arg("version", |arg| {
284 arg.help(crate::i18n::t(locale, "cli.version.flag"))
285 });
286 }
287
288 let sub_names = command
289 .get_subcommands()
290 .map(|sub| sub.get_name().to_string())
291 .collect::<Vec<_>>();
292 for name in sub_names {
293 command = command.mut_subcommand(name, |sub| localize_help_tree(sub, locale, false));
294 }
295
296 command
297}
298
299#[derive(Subcommand, Debug)]
300pub enum Command {
301 Flow(PassthroughArgs),
303 Pack(PassthroughArgs),
305 Component(PassthroughArgs),
307 #[command(subcommand)]
309 Config(ConfigCommand),
310 Coverage(CoverageArgs),
312 #[command(subcommand)]
314 Mcp(McpCommand),
315 Gui(PassthroughArgs),
317 #[command(subcommand)]
319 Secrets(SecretsCommand),
320 #[command(subcommand)]
322 Tools(ToolsCommand),
323 Install(InstallArgs),
325 Cbor(CborArgs),
327 Wizard(Box<WizardCommand>),
329}
330
331#[derive(Args, Debug, Clone)]
332#[command(disable_help_flag = true)]
333pub struct PassthroughArgs {
334 #[arg(
336 value_name = "ARGS",
337 trailing_var_arg = true,
338 allow_hyphen_values = true
339 )]
340 pub args: Vec<OsString>,
341}
342
343#[derive(Subcommand, Debug)]
344pub enum McpCommand {
345 Doctor(McpDoctorArgs),
347}
348
349#[derive(Args, Debug)]
350pub struct McpDoctorArgs {
351 pub provider: String,
353 #[arg(long = "json")]
355 pub json: bool,
356}
357
358#[derive(Subcommand, Debug)]
359pub enum ConfigCommand {
360 Set(ConfigSetArgs),
362}
363
364#[derive(Subcommand, Debug)]
365pub enum ToolsCommand {
366 Install(ToolsInstallArgs),
368}
369
370#[derive(Subcommand, Debug)]
371pub enum InstallSubcommand {
372 Tools(ToolsInstallArgs),
374}
375
376#[derive(Args, Debug)]
377pub struct InstallArgs {
378 #[command(subcommand)]
379 pub command: Option<InstallSubcommand>,
380 #[arg(long = "tenant")]
382 pub tenant: Option<String>,
383 #[arg(long = "token")]
385 pub token: Option<String>,
386 #[arg(long = "bin-dir")]
388 pub bin_dir: Option<PathBuf>,
389 #[arg(long = "docs-dir")]
391 pub docs_dir: Option<PathBuf>,
392 #[arg(long = "locale")]
394 pub locale: Option<String>,
395}
396
397#[derive(Args, Debug)]
398pub struct ToolsInstallArgs {
399 #[arg(long = "latest")]
401 pub latest: bool,
402}
403
404#[derive(Args, Debug)]
405pub struct ConfigSetArgs {
406 pub key: String,
408 pub value: String,
410 #[arg(long = "file")]
412 pub file: Option<PathBuf>,
413}
414
415#[derive(Args, Debug)]
416pub struct CborArgs {
417 #[arg(value_name = "PATH")]
419 pub path: PathBuf,
420}
421
422#[derive(Args, Debug, Clone)]
423pub struct CoverageArgs {
424 #[arg(long = "skip-run")]
426 pub skip_run: bool,
427}
428
429#[derive(Args, Debug, Clone)]
430pub struct WizardCommand {
431 #[command(subcommand)]
432 pub command: Option<WizardSubcommand>,
433 #[command(flatten)]
434 pub launch: WizardLaunchArgs,
435}
436
437#[derive(Subcommand, Debug, Clone)]
438pub enum WizardSubcommand {
439 Validate(WizardValidateArgs),
441 Apply(WizardApplyArgs),
443}
444
445#[derive(Args, Debug, Clone)]
446pub struct WizardLaunchArgs {
447 #[arg(long = "answers")]
449 pub answers: Option<String>,
450 #[arg(long = "frontend", default_value = "json")]
452 pub frontend: String,
453 #[arg(long = "locale")]
455 pub locale: Option<String>,
456 #[arg(long = "emit-answers")]
458 pub emit_answers: Option<PathBuf>,
459 #[arg(long = "schema")]
461 pub schema: bool,
462 #[arg(long = "schema-version")]
464 pub schema_version: Option<String>,
465 #[arg(long = "migrate")]
467 pub migrate: bool,
468 #[arg(long = "out")]
470 pub out: Option<PathBuf>,
471 #[arg(long = "dry-run")]
473 pub dry_run: bool,
474 #[arg(long = "yes")]
476 pub yes: bool,
477 #[arg(long = "non-interactive")]
479 pub non_interactive: bool,
480 #[arg(long = "unsafe-commands")]
482 pub unsafe_commands: bool,
483 #[arg(long = "allow-destructive")]
485 pub allow_destructive: bool,
486}
487
488#[derive(Args, Debug, Clone)]
489pub struct WizardValidateArgs {
490 #[arg(long = "answers")]
492 pub answers: String,
493 #[arg(long = "frontend", default_value = "json")]
495 pub frontend: String,
496 #[arg(long = "locale")]
498 pub locale: Option<String>,
499 #[arg(long = "emit-answers")]
501 pub emit_answers: Option<PathBuf>,
502 #[arg(long = "schema-version")]
504 pub schema_version: Option<String>,
505 #[arg(long = "migrate")]
507 pub migrate: bool,
508 #[arg(long = "out")]
510 pub out: Option<PathBuf>,
511}
512
513#[derive(Args, Debug, Clone)]
514pub struct WizardApplyArgs {
515 #[arg(long = "answers")]
517 pub answers: String,
518 #[arg(long = "frontend", default_value = "json")]
520 pub frontend: String,
521 #[arg(long = "locale")]
523 pub locale: Option<String>,
524 #[arg(long = "emit-answers")]
526 pub emit_answers: Option<PathBuf>,
527 #[arg(long = "schema-version")]
529 pub schema_version: Option<String>,
530 #[arg(long = "migrate")]
532 pub migrate: bool,
533 #[arg(long = "out")]
535 pub out: Option<PathBuf>,
536 #[arg(long = "yes")]
538 pub yes: bool,
539 #[arg(long = "non-interactive")]
541 pub non_interactive: bool,
542 #[arg(long = "unsafe-commands")]
544 pub unsafe_commands: bool,
545 #[arg(long = "allow-destructive")]
547 pub allow_destructive: bool,
548}