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