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_version", |arg| {
163                    arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
164                })
165                .mut_arg("migrate", |arg| {
166                    arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
167                })
168                .mut_arg("out", |arg| {
169                    arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
170                })
171                .mut_arg("dry_run", |arg| {
172                    arg.help(crate::i18n::t(locale, "cli.command.wizard.dry_run"))
173                })
174                .mut_arg("yes", |arg| {
175                    arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
176                })
177                .mut_arg("non_interactive", |arg| {
178                    arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
179                })
180                .mut_arg("unsafe_commands", |arg| {
181                    arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
182                })
183                .mut_arg("allow_destructive", |arg| {
184                    arg.help(crate::i18n::t(
185                        locale,
186                        "cli.command.wizard.allow_destructive",
187                    ))
188                })
189                .mut_subcommand("validate", |sub| {
190                    sub.about(crate::i18n::t(locale, "cli.command.wizard.validate.about"))
191                        .mut_arg("answers", |arg| {
192                            arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
193                        })
194                        .mut_arg("frontend", |arg| {
195                            arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
196                        })
197                        .mut_arg("locale", |arg| {
198                            arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
199                        })
200                        .mut_arg("emit_answers", |arg| {
201                            arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
202                        })
203                        .mut_arg("schema_version", |arg| {
204                            arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
205                        })
206                        .mut_arg("migrate", |arg| {
207                            arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
208                        })
209                        .mut_arg("out", |arg| {
210                            arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
211                        })
212                })
213                .mut_subcommand("apply", |sub| {
214                    sub.about(crate::i18n::t(locale, "cli.command.wizard.apply.about"))
215                        .mut_arg("answers", |arg| {
216                            arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
217                        })
218                        .mut_arg("frontend", |arg| {
219                            arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
220                        })
221                        .mut_arg("locale", |arg| {
222                            arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
223                        })
224                        .mut_arg("emit_answers", |arg| {
225                            arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
226                        })
227                        .mut_arg("schema_version", |arg| {
228                            arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
229                        })
230                        .mut_arg("migrate", |arg| {
231                            arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
232                        })
233                        .mut_arg("out", |arg| {
234                            arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
235                        })
236                        .mut_arg("yes", |arg| {
237                            arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
238                        })
239                        .mut_arg("non_interactive", |arg| {
240                            arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
241                        })
242                        .mut_arg("unsafe_commands", |arg| {
243                            arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
244                        })
245                        .mut_arg("allow_destructive", |arg| {
246                            arg.help(crate::i18n::t(
247                                locale,
248                                "cli.command.wizard.allow_destructive",
249                            ))
250                        })
251                })
252        });
253
254    localize_help_tree(command, locale, true)
255}
256
257fn localize_help_tree(mut command: clap::Command, locale: &str, is_root: bool) -> clap::Command {
258    command = command
259        .disable_help_subcommand(true)
260        .disable_help_flag(true);
261    let arg_ids = command
262        .get_arguments()
263        .map(|arg| arg.get_id().as_str().to_string())
264        .collect::<Vec<_>>();
265    if arg_ids.iter().any(|id| id == "help") {
266        command = command.mut_arg("help", |arg| {
267            arg.help(crate::i18n::t(locale, "cli.help.flag"))
268        });
269    } else {
270        command = command.arg(
271            Arg::new("help")
272                .short('h')
273                .long("help")
274                .action(ArgAction::Help)
275                .help(crate::i18n::t(locale, "cli.help.flag")),
276        );
277    }
278    if is_root && arg_ids.iter().any(|id| id == "version") {
279        command = command.mut_arg("version", |arg| {
280            arg.help(crate::i18n::t(locale, "cli.version.flag"))
281        });
282    }
283
284    let sub_names = command
285        .get_subcommands()
286        .map(|sub| sub.get_name().to_string())
287        .collect::<Vec<_>>();
288    for name in sub_names {
289        command = command.mut_subcommand(name, |sub| localize_help_tree(sub, locale, false));
290    }
291
292    command
293}
294
295#[derive(Subcommand, Debug)]
296pub enum Command {
297    /// cli.command.flow.about
298    Flow(PassthroughArgs),
299    /// cli.command.pack.about
300    Pack(PassthroughArgs),
301    /// cli.command.component.about
302    Component(PassthroughArgs),
303    /// cli.command.config.about
304    #[command(subcommand)]
305    Config(ConfigCommand),
306    /// cli.command.coverage.about
307    Coverage(CoverageArgs),
308    /// cli.command.mcp.about
309    #[command(subcommand)]
310    Mcp(McpCommand),
311    /// cli.command.gui.about
312    Gui(PassthroughArgs),
313    /// cli.command.secrets.about
314    #[command(subcommand)]
315    Secrets(SecretsCommand),
316    /// cli.command.tools.about
317    #[command(subcommand)]
318    Tools(ToolsCommand),
319    /// cli.command.install.about
320    Install(InstallArgs),
321    /// cli.command.cbor.about
322    Cbor(CborArgs),
323    /// cli.command.wizard.about
324    Wizard(Box<WizardCommand>),
325}
326
327#[derive(Args, Debug, Clone)]
328#[command(disable_help_flag = true)]
329pub struct PassthroughArgs {
330    /// cli.command.passthrough.args
331    #[arg(
332        value_name = "ARGS",
333        trailing_var_arg = true,
334        allow_hyphen_values = true
335    )]
336    pub args: Vec<OsString>,
337}
338
339#[derive(Subcommand, Debug)]
340pub enum McpCommand {
341    /// cli.command.mcp.doctor.about
342    Doctor(McpDoctorArgs),
343}
344
345#[derive(Args, Debug)]
346pub struct McpDoctorArgs {
347    /// cli.command.mcp.doctor.provider
348    pub provider: String,
349    /// cli.command.mcp.doctor.json
350    #[arg(long = "json")]
351    pub json: bool,
352}
353
354#[derive(Subcommand, Debug)]
355pub enum ConfigCommand {
356    /// cli.command.config.set.about
357    Set(ConfigSetArgs),
358}
359
360#[derive(Subcommand, Debug)]
361pub enum ToolsCommand {
362    /// cli.command.tools.install.about
363    Install(ToolsInstallArgs),
364}
365
366#[derive(Subcommand, Debug)]
367pub enum InstallSubcommand {
368    /// cli.command.install.tools.about
369    Tools(ToolsInstallArgs),
370}
371
372#[derive(Args, Debug)]
373pub struct InstallArgs {
374    #[command(subcommand)]
375    pub command: Option<InstallSubcommand>,
376    /// cli.command.install.tenant
377    #[arg(long = "tenant")]
378    pub tenant: Option<String>,
379    /// cli.command.install.token
380    #[arg(long = "token")]
381    pub token: Option<String>,
382    /// cli.command.install.bin_dir
383    #[arg(long = "bin-dir")]
384    pub bin_dir: Option<PathBuf>,
385    /// cli.command.install.docs_dir
386    #[arg(long = "docs-dir")]
387    pub docs_dir: Option<PathBuf>,
388    /// cli.command.install.locale
389    #[arg(long = "locale")]
390    pub locale: Option<String>,
391}
392
393#[derive(Args, Debug)]
394pub struct ToolsInstallArgs {
395    /// cli.command.tools.install.latest
396    #[arg(long = "latest")]
397    pub latest: bool,
398}
399
400#[derive(Args, Debug)]
401pub struct ConfigSetArgs {
402    /// cli.command.config.set.key
403    pub key: String,
404    /// cli.command.config.set.value
405    pub value: String,
406    /// cli.command.config.set.file
407    #[arg(long = "file")]
408    pub file: Option<PathBuf>,
409}
410
411#[derive(Args, Debug)]
412pub struct CborArgs {
413    /// cli.command.cbor.path
414    #[arg(value_name = "PATH")]
415    pub path: PathBuf,
416}
417
418#[derive(Args, Debug, Clone)]
419pub struct CoverageArgs {
420    /// cli.command.coverage.skip_run
421    #[arg(long = "skip-run")]
422    pub skip_run: bool,
423}
424
425#[derive(Args, Debug, Clone)]
426pub struct WizardCommand {
427    #[command(subcommand)]
428    pub command: Option<WizardSubcommand>,
429    #[command(flatten)]
430    pub launch: WizardLaunchArgs,
431}
432
433#[derive(Subcommand, Debug, Clone)]
434pub enum WizardSubcommand {
435    /// cli.command.wizard.validate.about
436    Validate(WizardValidateArgs),
437    /// cli.command.wizard.apply.about
438    Apply(WizardApplyArgs),
439}
440
441#[derive(Args, Debug, Clone)]
442pub struct WizardLaunchArgs {
443    /// cli.command.wizard.answers (local path or http/https URL)
444    #[arg(long = "answers")]
445    pub answers: Option<String>,
446    /// cli.command.wizard.frontend
447    #[arg(long = "frontend", default_value = "json")]
448    pub frontend: String,
449    /// cli.command.wizard.locale
450    #[arg(long = "locale")]
451    pub locale: Option<String>,
452    /// cli.command.wizard.emit_answers
453    #[arg(long = "emit-answers")]
454    pub emit_answers: Option<PathBuf>,
455    /// cli.command.wizard.schema_version
456    #[arg(long = "schema-version")]
457    pub schema_version: Option<String>,
458    /// cli.command.wizard.migrate
459    #[arg(long = "migrate")]
460    pub migrate: bool,
461    /// cli.command.wizard.out
462    #[arg(long = "out")]
463    pub out: Option<PathBuf>,
464    /// cli.command.wizard.dry_run
465    #[arg(long = "dry-run")]
466    pub dry_run: bool,
467    /// cli.command.wizard.yes
468    #[arg(long = "yes")]
469    pub yes: bool,
470    /// cli.command.wizard.non_interactive
471    #[arg(long = "non-interactive")]
472    pub non_interactive: bool,
473    /// cli.command.wizard.unsafe_commands
474    #[arg(long = "unsafe-commands")]
475    pub unsafe_commands: bool,
476    /// cli.command.wizard.allow_destructive
477    #[arg(long = "allow-destructive")]
478    pub allow_destructive: bool,
479}
480
481#[derive(Args, Debug, Clone)]
482pub struct WizardValidateArgs {
483    /// cli.command.wizard.answers (local path or http/https URL)
484    #[arg(long = "answers")]
485    pub answers: String,
486    /// cli.command.wizard.frontend
487    #[arg(long = "frontend", default_value = "json")]
488    pub frontend: String,
489    /// cli.command.wizard.locale
490    #[arg(long = "locale")]
491    pub locale: Option<String>,
492    /// cli.command.wizard.emit_answers
493    #[arg(long = "emit-answers")]
494    pub emit_answers: Option<PathBuf>,
495    /// cli.command.wizard.schema_version
496    #[arg(long = "schema-version")]
497    pub schema_version: Option<String>,
498    /// cli.command.wizard.migrate
499    #[arg(long = "migrate")]
500    pub migrate: bool,
501    /// cli.command.wizard.out
502    #[arg(long = "out")]
503    pub out: Option<PathBuf>,
504}
505
506#[derive(Args, Debug, Clone)]
507pub struct WizardApplyArgs {
508    /// cli.command.wizard.answers (local path or http/https URL)
509    #[arg(long = "answers")]
510    pub answers: String,
511    /// cli.command.wizard.frontend
512    #[arg(long = "frontend", default_value = "json")]
513    pub frontend: String,
514    /// cli.command.wizard.locale
515    #[arg(long = "locale")]
516    pub locale: Option<String>,
517    /// cli.command.wizard.emit_answers
518    #[arg(long = "emit-answers")]
519    pub emit_answers: Option<PathBuf>,
520    /// cli.command.wizard.schema_version
521    #[arg(long = "schema-version")]
522    pub schema_version: Option<String>,
523    /// cli.command.wizard.migrate
524    #[arg(long = "migrate")]
525    pub migrate: bool,
526    /// cli.command.wizard.out
527    #[arg(long = "out")]
528    pub out: Option<PathBuf>,
529    /// cli.command.wizard.yes
530    #[arg(long = "yes")]
531    pub yes: bool,
532    /// cli.command.wizard.non_interactive
533    #[arg(long = "non-interactive")]
534    pub non_interactive: bool,
535    /// cli.command.wizard.unsafe_commands
536    #[arg(long = "unsafe-commands")]
537    pub unsafe_commands: bool,
538    /// cli.command.wizard.allow_destructive
539    #[arg(long = "allow-destructive")]
540    pub allow_destructive: bool,
541}