Skip to main content

greentic_dev/
cli.rs

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    /// cli.command.flow.about
302    Flow(PassthroughArgs),
303    /// cli.command.pack.about
304    Pack(PassthroughArgs),
305    /// cli.command.component.about
306    Component(PassthroughArgs),
307    /// cli.command.config.about
308    #[command(subcommand)]
309    Config(ConfigCommand),
310    /// cli.command.coverage.about
311    Coverage(CoverageArgs),
312    /// cli.command.mcp.about
313    #[command(subcommand)]
314    Mcp(McpCommand),
315    /// cli.command.gui.about
316    Gui(PassthroughArgs),
317    /// cli.command.secrets.about
318    #[command(subcommand)]
319    Secrets(SecretsCommand),
320    /// cli.command.tools.about
321    #[command(subcommand)]
322    Tools(ToolsCommand),
323    /// cli.command.install.about
324    Install(InstallArgs),
325    /// cli.command.cbor.about
326    Cbor(CborArgs),
327    /// cli.command.wizard.about
328    Wizard(Box<WizardCommand>),
329}
330
331#[derive(Args, Debug, Clone)]
332#[command(disable_help_flag = true)]
333pub struct PassthroughArgs {
334    /// cli.command.passthrough.args
335    #[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    /// cli.command.mcp.doctor.about
346    Doctor(McpDoctorArgs),
347}
348
349#[derive(Args, Debug)]
350pub struct McpDoctorArgs {
351    /// cli.command.mcp.doctor.provider
352    pub provider: String,
353    /// cli.command.mcp.doctor.json
354    #[arg(long = "json")]
355    pub json: bool,
356}
357
358#[derive(Subcommand, Debug)]
359pub enum ConfigCommand {
360    /// cli.command.config.set.about
361    Set(ConfigSetArgs),
362}
363
364#[derive(Subcommand, Debug)]
365pub enum ToolsCommand {
366    /// cli.command.tools.install.about
367    Install(ToolsInstallArgs),
368}
369
370#[derive(Subcommand, Debug)]
371pub enum InstallSubcommand {
372    /// cli.command.install.tools.about
373    Tools(ToolsInstallArgs),
374}
375
376#[derive(Args, Debug)]
377pub struct InstallArgs {
378    #[command(subcommand)]
379    pub command: Option<InstallSubcommand>,
380    /// cli.command.install.tenant
381    #[arg(long = "tenant")]
382    pub tenant: Option<String>,
383    /// cli.command.install.token
384    #[arg(long = "token")]
385    pub token: Option<String>,
386    /// cli.command.install.bin_dir
387    #[arg(long = "bin-dir")]
388    pub bin_dir: Option<PathBuf>,
389    /// cli.command.install.docs_dir
390    #[arg(long = "docs-dir")]
391    pub docs_dir: Option<PathBuf>,
392    /// cli.command.install.locale
393    #[arg(long = "locale")]
394    pub locale: Option<String>,
395}
396
397#[derive(Args, Debug)]
398pub struct ToolsInstallArgs {
399    /// cli.command.tools.install.latest
400    #[arg(long = "latest")]
401    pub latest: bool,
402}
403
404#[derive(Args, Debug)]
405pub struct ConfigSetArgs {
406    /// cli.command.config.set.key
407    pub key: String,
408    /// cli.command.config.set.value
409    pub value: String,
410    /// cli.command.config.set.file
411    #[arg(long = "file")]
412    pub file: Option<PathBuf>,
413}
414
415#[derive(Args, Debug)]
416pub struct CborArgs {
417    /// cli.command.cbor.path
418    #[arg(value_name = "PATH")]
419    pub path: PathBuf,
420}
421
422#[derive(Args, Debug, Clone)]
423pub struct CoverageArgs {
424    /// cli.command.coverage.skip_run
425    #[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    /// cli.command.wizard.validate.about
440    Validate(WizardValidateArgs),
441    /// cli.command.wizard.apply.about
442    Apply(WizardApplyArgs),
443}
444
445#[derive(Args, Debug, Clone)]
446pub struct WizardLaunchArgs {
447    /// cli.command.wizard.answers (local path or http/https URL)
448    #[arg(long = "answers")]
449    pub answers: Option<String>,
450    /// cli.command.wizard.frontend
451    #[arg(long = "frontend", default_value = "json")]
452    pub frontend: String,
453    /// cli.command.wizard.locale
454    #[arg(long = "locale")]
455    pub locale: Option<String>,
456    /// cli.command.wizard.emit_answers
457    #[arg(long = "emit-answers")]
458    pub emit_answers: Option<PathBuf>,
459    /// cli.command.wizard.schema
460    #[arg(long = "schema")]
461    pub schema: bool,
462    /// cli.command.wizard.schema_version
463    #[arg(long = "schema-version")]
464    pub schema_version: Option<String>,
465    /// cli.command.wizard.migrate
466    #[arg(long = "migrate")]
467    pub migrate: bool,
468    /// cli.command.wizard.out
469    #[arg(long = "out")]
470    pub out: Option<PathBuf>,
471    /// cli.command.wizard.dry_run
472    #[arg(long = "dry-run")]
473    pub dry_run: bool,
474    /// cli.command.wizard.yes
475    #[arg(long = "yes")]
476    pub yes: bool,
477    /// cli.command.wizard.non_interactive
478    #[arg(long = "non-interactive")]
479    pub non_interactive: bool,
480    /// cli.command.wizard.unsafe_commands
481    #[arg(long = "unsafe-commands")]
482    pub unsafe_commands: bool,
483    /// cli.command.wizard.allow_destructive
484    #[arg(long = "allow-destructive")]
485    pub allow_destructive: bool,
486}
487
488#[derive(Args, Debug, Clone)]
489pub struct WizardValidateArgs {
490    /// cli.command.wizard.answers (local path or http/https URL)
491    #[arg(long = "answers")]
492    pub answers: String,
493    /// cli.command.wizard.frontend
494    #[arg(long = "frontend", default_value = "json")]
495    pub frontend: String,
496    /// cli.command.wizard.locale
497    #[arg(long = "locale")]
498    pub locale: Option<String>,
499    /// cli.command.wizard.emit_answers
500    #[arg(long = "emit-answers")]
501    pub emit_answers: Option<PathBuf>,
502    /// cli.command.wizard.schema_version
503    #[arg(long = "schema-version")]
504    pub schema_version: Option<String>,
505    /// cli.command.wizard.migrate
506    #[arg(long = "migrate")]
507    pub migrate: bool,
508    /// cli.command.wizard.out
509    #[arg(long = "out")]
510    pub out: Option<PathBuf>,
511}
512
513#[derive(Args, Debug, Clone)]
514pub struct WizardApplyArgs {
515    /// cli.command.wizard.answers (local path or http/https URL)
516    #[arg(long = "answers")]
517    pub answers: String,
518    /// cli.command.wizard.frontend
519    #[arg(long = "frontend", default_value = "json")]
520    pub frontend: String,
521    /// cli.command.wizard.locale
522    #[arg(long = "locale")]
523    pub locale: Option<String>,
524    /// cli.command.wizard.emit_answers
525    #[arg(long = "emit-answers")]
526    pub emit_answers: Option<PathBuf>,
527    /// cli.command.wizard.schema_version
528    #[arg(long = "schema-version")]
529    pub schema_version: Option<String>,
530    /// cli.command.wizard.migrate
531    #[arg(long = "migrate")]
532    pub migrate: bool,
533    /// cli.command.wizard.out
534    #[arg(long = "out")]
535    pub out: Option<PathBuf>,
536    /// cli.command.wizard.yes
537    #[arg(long = "yes")]
538    pub yes: bool,
539    /// cli.command.wizard.non_interactive
540    #[arg(long = "non-interactive")]
541    pub non_interactive: bool,
542    /// cli.command.wizard.unsafe_commands
543    #[arg(long = "unsafe-commands")]
544    pub unsafe_commands: bool,
545    /// cli.command.wizard.allow_destructive
546    #[arg(long = "allow-destructive")]
547    pub allow_destructive: bool,
548}