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